<?xml version='1.0' encoding='UTF-8'?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
  <channel>
    <title>Andy Kong's Blog</title>
    <link>https://andykong.org/rss.xml</link>
    <description>Thoughts and Work</description>
    <atom:link href="https://andykong.org/rss.xml" rel="self"/>
    <docs>http://www.rssboard.org/rss-specification</docs>
    <generator>python-feedgen</generator>
    <lastBuildDate>Thu, 09 Jul 2026 23:47:40 +0000</lastBuildDate>
    <item>
      <title>Fast automated mobile E2E testing — the unwritten manual</title>
      <link>https://andykong.org/blog/fast_mobile_e2e/</link>
      <description>A practical field guide to speeding up automated mobile E2E tests by cutting snapshot, tap, verification, and backend-check latency.</description>
      <content:encoded><![CDATA[<html><body><p>Most "drive your phone with code" stacks (XCUITest/WDA, uiautomator, Appium, mobile-mcp) work by taking a snapshot of the UI tree, parsing it, and tapping coordinates from the result. That works. It is also dramatically slower than it has any right to be — by default. This is the document I wanted before I started.</p>
<p>Concrete numbers from one app (SwiftUI iOS + Compose Android):</p>
<table>
<thead>
<tr>
<th>chain</th>
<th>unoptimized</th>
<th>optimized</th>
<th>speedup</th>
</tr>
</thead>
<tbody>
<tr>
<td>Android <code>launch → advanced settings</code></td>
<td>14.0s</td>
<td>5.3s</td>
<td>2.6×</td>
</tr>
<tr>
<td>iOS <code>launch → advanced settings</code></td>
<td>14.4s</td>
<td>4.9s</td>
<td>2.9×</td>
</tr>
<tr>
<td>iOS deep-link <code>launch → advanced</code></td>
<td>—</td>
<td>1.25s</td>
<td>11.5×</td>
</tr>
<tr>
<td>Android E2E suite (8 tests)</td>
<td>161s</td>
<td>54s</td>
<td>3.0×</td>
</tr>
<tr>
<td>iOS E2E suite (8 tests)</td>
<td>175s+</td>
<td>56s</td>
<td>3.1×</td>
</tr>
</tbody>
</table>
<p>Same hardware, same tests, no parallelism, no flaky-test retries.</p>
<pre><code>{
  "headline_chains": [
    {"chain": "Android: launch → advanced settings",     "before_s": 14.0, "after_s": 5.3,  "speedup": 2.6},
    {"chain": "iOS: launch → advanced settings",         "before_s": 14.4, "after_s": 4.9,  "speedup": 2.9},
    {"chain": "iOS: launch → advanced (deep link)",      "before_s": 14.4, "after_s": 1.25, "speedup": 11.5},
    {"chain": "Android E2E suite (8 tests)",             "before_s": 161,  "after_s": 54,   "speedup": 3.0},
    {"chain": "iOS E2E suite (8 tests)",                 "before_s": 175,  "after_s": 56,   "speedup": 3.1}
  ]
}
</code></pre>
<p>The wins come from a small set of repeating patterns. Here they are.</p>
<div class="toc"><h2 id="table-of-contents" style="margin:0px;">Table of Contents</h2>1. <a href="#1-the-four-phases-of-any-ui-step">1. The four phases of any UI step</a><br/>2. <a href="#2-see--making-the-snapshot-cheap">2. SEE — making the snapshot cheap</a><br/>3. <a href="#3-act--the-hidden-quiescence-tax">3. ACT — the hidden quiescence tax</a><br/>4. <a href="#4-verify--never-silentpass">4. VERIFY — never silent-pass</a><br/>5. <a href="#5-decide--surviving-ui-rotation">5. DECIDE — surviving UI rotation</a><br/>6. <a href="#6-recovery--the-things-that-break">6. RECOVERY — the things that break</a><br/>7. <a href="#7-delays--sleep-is-the-enemy">7. DELAYS — sleep is the enemy</a><br/>8. <a href="#8-architecting-for-testability--existing-app-vs-greenfield">8. ARCHITECTING for testability — existing app vs greenfield</a><br/>9. <a href="#9-db--server-verification--talk-directly">9. DB / SERVER VERIFICATION — talk directly</a><br/>10. <a href="#10-latency-budgets--what-to-actually-measure">10. LATENCY BUDGETS — what to actually measure</a><br/>11. <a href="#11-iteration-loop--the-metarule">11. ITERATION LOOP — the meta-rule</a><br/>12. <a href="#12-tldr--defaults-to-set-on-every-project">12. TL;DR — defaults to set on every project</a><br/></div>
<hr/>
<hr/>
<h2 id="1-the-four-phases-of-any-ui-step">1. The four phases of any UI step</h2>
<pre><code>SEE → DECIDE → ACT → VERIFY
 ↓      ↓      ↓       ↓
dump  parse  tap   re-read
</code></pre>
<p>Every step you write spends time on each phase. Most slow tests are slow because <strong>SEE</strong> dominates and <strong>VERIFY</strong> is omitted. Most flaky tests are flaky because <strong>VERIFY</strong> is wrong. Most stale tests rot because <strong>DECIDE</strong> hard-codes UI strings.</p>
<p>Optimize them in this order:</p>
<ol>
<li><strong>SEE</strong> is the budget killer. Default snapshot tools serialize the entire accessibility tree. On dense screens that's 1-3s on iOS, 2-3s on Android. Your action takes 50ms. Reduce snapshots, scope them, or skip them.</li>
<li><strong>VERIFY</strong> must be loud. Soft warnings ("could not find element X; skipping") = silent failure later. Always have a failing path.</li>
<li><strong>DECIDE</strong> must survive UI rotation. Match on stable identifiers, not human-facing copy. Fall back gracefully when the copy rotates.</li>
<li><strong>ACT</strong> is usually fine. Taps are fast. The hidden tax is the framework's "wait for animations" after each action — kill it.</li>
</ol>
<p><img alt="Phase breakdown for one chain (iOS, launch → advanced settings) before vs after, showing SEE shrinking from 9.2s to 1.2s while VERIFY grows from 0.6s to 1.5s; total drops 14.4s → 4.9s." src="/static/blog_fast_mobile_e2e/02_phases.png"/></p>
<pre><code>{
  "chart": "phase_breakdown",
  "title": "iOS, launch → advanced — phase breakdown",
  "phases": ["SEE", "DECIDE", "ACT", "VERIFY"],
  "before_ms": {"SEE": 9200, "DECIDE": 200, "ACT": 4400, "VERIFY": 600,  "total": 14400},
  "after_ms":  {"SEE": 1200, "DECIDE": 200, "ACT": 2000, "VERIFY": 1500, "total":  4900}
}
</code></pre>
<hr/>
<h2 id="2-see--making-the-snapshot-cheap">2. SEE — making the snapshot cheap</h2>
<p>The default APIs assume you want everything:</p>
<ul>
<li>iOS WDA: <code>GET /session/$SID/source</code> returns the full UIKit tree as XML. ~1s on a sparse screen, ~3s on a dense one.</li>
<li>Android: <code>adb shell uiautomator dump</code> walks the whole accessibility tree. ~2.2s/call, occasional SIGKILL during animations.</li>
</ul>
<p>Three escape hatches, in order of preference:</p>
<h3>a) Don't snapshot. Use the framework's "find one" endpoint.</h3>
<p>Both WDA and uiautomator2-on-device have a per-element find endpoint that runs the predicate server-side and returns just the matched element ID. WDA: <code>POST /element</code> with <code>using: "predicate string"</code>. uiautomator2: same idea via JSONRPC.</p>
<pre><code>WDA full /source:                      ~2700ms on a dense screen
WDA POST /element by predicate:        ~300ms
</code></pre>
<p>Then click via <code>POST /element/$ID/click</code>. Skips the parse phase entirely.</p>
<p><strong>Catch</strong>: predicate find returns ONE match by document order. If the predicate is ambiguous ("Settings" matches both a row and a NavigationBar title), the cached element ID can go stale across a screen transition and your click 404s. Scope by <code>type</code>:</p>
<pre><code>type == "XCUIElementTypeButton" AND name == "Settings"
</code></pre>
<p>Same kind of trap on Android — multiple <code>4h</code> buttons exist if there are multiple charts. Document-order disambiguation works when the chart you want is rendered first; verify with a Y-coord check if it isn't.</p>
<h3>b) Persistent on-device server (Android-specific giant win)</h3>
<p><code>adb shell uiautomator dump</code> is slow because it cold-spawns a UIAutomator client every time. The community solved this years ago with <a href="https://github.com/openatx/uiautomator2">openatx/uiautomator2</a>: a tiny on-device JSONRPC server that holds a warm UIAutomator handle and answers <code>dumpWindowHierarchy</code> in ~200ms.</p>
<p>Setup is one command, then the script handles the rest:</p>
<pre><code>pip install uiautomator2
python -m uiautomator2 init   # pushes the .jar to /data/local/tmp/u2.jar
</code></pre>
<p>The server is <code>app_process</code> and dies when the spawning shell exits, so detach when starting:</p>
<pre><code>adb shell 'nohup sh -c "CLASSPATH=/data/local/tmp/u2.jar exec app_process / com.wetest.uia2.Main" &gt;/dev/null 2&gt;&amp;1 &amp;'
</code></pre>
<p>After that, talk to it via raw HTTP — no Python lib needed in the hot path:</p>
<pre><code>import json, urllib.request
req = urllib.request.Request("http://127.0.0.1:9008/jsonrpc/0",
    data=json.dumps({"jsonrpc":"2.0","id":1,
                     "method":"dumpWindowHierarchy",
                     "params":[False, 50]}).encode(),
    headers={"Content-Type":"application/json"})
xml = json.loads(urllib.request.urlopen(req).read())["result"]
</code></pre>
<p>10× faster, zero deps in your script (it's stdlib).</p>
<p>iOS already has this — WDA itself is the persistent server. The lever there is bypassing <code>/source</code> (option a).</p>
<h3>c) Skip the snapshot when you don't need it</h3>
<p>If you're tapping a known location (bottom-nav tab, a hardcoded coordinate from a stable layout), don't snapshot first. The first source dump of a chain is usually the most expensive — the screen has the most rendered content. Save it.</p>
<pre><code>Hardcoded Pixel 7 bottom-nav coords:
  DASHBOARD_TAB = (264, 2232)
  PROFILE_TAB   = (815, 2232)

Hardcoded iPhone 13 (390×844 logical):
  DEVICE_TAB  = (65, 786)
  PROFILE_TAB = (325, 786)
</code></pre>
<p>Re-derive when you change device. Document them with the device model so the next person knows when they're stale.</p>
<h3>d) Settings tweaks that compound</h3>
<p>Both stacks have settings to bound the snapshot:</p>
<ul>
<li>WDA: <code>POST /session/$SID/appium/settings</code> with <code>snapshotMaxDepth: 30, shouldUseCompactResponses: true</code>. Modest but free.</li>
<li>uiautomator2 dump: <code>params: [compressed=False, max_depth=50]</code> — <code>compressed</code> strips invisible decoration nodes (only ~7% on Compose UIs, more on legacy Views).</li>
</ul>
<p><img alt="SEE-phase operations sorted by latency, log scale: hardcoded coordinate at ~1ms, openatx u2 at 200ms, predicate find at 300ms, screencap 330ms, WDA screenshot 600ms, WDA source on a sparse screen 1s, uiautomator dump 2.2s, WDA source on a dense screen 2.7s." src="/static/blog_fast_mobile_e2e/04_see.png"/></p>
<pre><code>{
  "chart": "see_operations",
  "operations": [
    {"name": "hardcoded coordinate (skip SEE)",      "ms": 1,    "tier": "fast"},
    {"name": "openatx u2 dumpWindowHierarchy",       "ms": 200,  "tier": "fast"},
    {"name": "WDA POST /element (predicate find)",   "ms": 300,  "tier": "fast"},
    {"name": "adb exec-out screencap -p",            "ms": 330,  "tier": "situational"},
    {"name": "WDA /screenshot",                      "ms": 600,  "tier": "situational"},
    {"name": "WDA /source (sparse screen)",          "ms": 1000, "tier": "situational"},
    {"name": "adb shell uiautomator dump",           "ms": 2200, "tier": "slow"},
    {"name": "WDA /source (dense screen)",           "ms": 2700, "tier": "slow"}
  ]
}
</code></pre>
<hr/>
<h2 id="3-act--the-hidden-quiescence-tax">3. ACT — the hidden quiescence tax</h2>
<p>WDA's biggest non-obvious cost: it waits for the app to be "idle" (no pending animations or async work) AFTER every action. Default ~1.1s per tap. <strong>Disable this:</strong></p>
<pre><code>session = POST("/session", {
  "capabilities": {
    "alwaysMatch": {
      "bundleId": "...",
      "waitForIdleTimeout": 0,   # ← single biggest iOS lever
    }
  }
})
</code></pre>
<p>Per-tap cost drops to ~0.5s. Across a 4-tap chain that's 2.5s saved.</p>
<p>Android <code>adb shell input tap</code> doesn't have this overhead — it returns in ~70ms. The whole iOS-vs-Android per-tap floor gap (~10×) is essentially this one capability.</p>
<p>You can re-enable selectively when you actually need the wait — for instance, before reading state from a screen that's mid-transition.</p>
<p><img alt="ACT-phase operations sorted by latency, log scale: adb input tap 70ms, keyevent 75ms, swipe 100ms, am start (deep link) 150ms, devicectl 240ms, WDA tap with idle disabled 500ms, WDA URL launch 1.1s, WDA tap with default idle wait 1.1s." src="/static/blog_fast_mobile_e2e/06_act.png"/></p>
<pre><code>{
  "chart": "act_operations",
  "operations": [
    {"name": "adb shell input tap",                  "ms": 70,   "tier": "fast"},
    {"name": "adb shell input keyevent",             "ms": 75,   "tier": "fast"},
    {"name": "adb shell input swipe",                "ms": 100,  "tier": "fast"},
    {"name": "adb shell am start (deep link)",       "ms": 150,  "tier": "fast"},
    {"name": "xcrun devicectl process launch",       "ms": 240,  "tier": "fast"},
    {"name": "WDA /wda/tap (waitForIdle=0)",         "ms": 500,  "tier": "situational"},
    {"name": "WDA URL launch (deep link)",           "ms": 1100, "tier": "situational"},
    {"name": "WDA /wda/tap (waitForIdle default)",   "ms": 1100, "tier": "slow"}
  ]
}
</code></pre>
<hr/>
<h2 id="4-verify--never-silentpass">4. VERIFY — never silent-pass</h2>
<p>The biggest class of mobile-test bugs: tests that "pass" while doing nothing useful. Pattern:</p>
<pre><code>btn = find(text="Device")
if btn:
    tap(btn)
else:
    log.warn("no Device tab; assuming we're already on it")
# ... rest of test runs against whatever screen happens to be up
</code></pre>
<p>If the tab gets renamed (we found one called <code>Device</code> that became <code>Daily</code> between two memory-snapshots), the warn fires once, the test continues, and you spend 30 seconds taking screenshots of the wrong screen. The test passes. You don't notice for weeks.</p>
<p><strong>Fix pattern:</strong></p>
<pre><code>for label in ("Daily", "Device", "Dashboard"):     # all known historical names
    btn = find(text=label)
    if btn:
        tap(btn)
        # VERIFY post-tap state with a marker known only to this screen
        if find(text="Heart Rate", kind="StaticText"):
            return
pytest.fail(
    "could not navigate to dashboard — none of "
    "Daily/Device/Dashboard buttons led to a screen with 'Heart Rate'. "
    "Has the tab label rotated again?"
)
</code></pre>
<p>The verifier should be:</p>
<ul>
<li><strong>Strictly post-condition-shaped</strong> ("Heart Rate is visible") not pre-condition-shaped ("the tab button I tapped was the right one").</li>
<li><strong>Specific to the destination</strong>, not generic ("there's a button somewhere").</li>
<li><strong>Allowed to fail</strong>, with a useful message that points the next maintainer at what changed.</li>
</ul>
<p>Same rule for any optional batch action. A timescale-buttons loop used to do <code>for label in ("4h","1d","1w","2w"): if not _tap(): log.warn(...)</code>. We changed it to track misses and <code>pytest.fail</code> if all 4 missed. Silent partial failure → loud total failure.</p>
<p><img alt="VERIFY-phase operations sorted by latency, log scale: direct DB SQL warm 80ms, u2 poll-find 200ms, predicate find 300ms, screencap 330ms, WDA screenshot 600ms, ssh+python warm 900ms, WDA source sparse 1s, WDA source dense 2.7s, ssh+python cold 6.7s." src="/static/blog_fast_mobile_e2e/07_verify.png"/></p>
<pre><code>{
  "chart": "verify_operations",
  "operations": [
    {"name": "Direct managed-DB SQL (warm pool)",    "ms": 80,   "tier": "fast"},
    {"name": "openatx u2 dump (poll-find)",          "ms": 200,  "tier": "fast"},
    {"name": "WDA POST /element (predicate)",        "ms": 300,  "tier": "fast"},
    {"name": "adb exec-out screencap -p",            "ms": 330,  "tier": "situational"},
    {"name": "WDA /screenshot",                      "ms": 600,  "tier": "situational"},
    {"name": "ssh + python -c (warm)",               "ms": 900,  "tier": "situational"},
    {"name": "WDA /source (sparse screen)",          "ms": 1000, "tier": "situational"},
    {"name": "WDA /source (dense screen)",           "ms": 2700, "tier": "slow"},
    {"name": "ssh + python -c (cold)",               "ms": 6700, "tier": "slow"}
  ]
}
</code></pre>
<hr/>
<h2 id="5-decide--surviving-ui-rotation">5. DECIDE — surviving UI rotation</h2>
<p>Human-facing copy rotates. Three real ones from the same app within 2 weeks:</p>
<ul>
<li>"Solar" → "Light exposure" (a segmented control)</li>
<li>"Device" → "Daily" (a bottom dashboard tab)</li>
<li>"Account" → "ACCOUNT" (a section header — case sensitivity)</li>
</ul>
<p>Mitigation:</p>
<ol>
<li><strong>Match on accessibility identifier when possible</strong>. SwiftUI <code>.accessibilityIdentifier("dashboard.heartrate.timescale.4h")</code>. Compose <code>Modifier.semantics { testTag = "dashboard.heartrate.timescale.4h" }</code>. Doesn't ship to users; doesn't rotate.</li>
<li><strong>Match on multiple known names</strong>. <code>find(text in {"Daily","Device","Dashboard"})</code>. Cheaper than introducing accessibility identifiers retroactively.</li>
<li><strong>Annotate the test memo with the rename history</strong>. When the next rename happens, the maintainer knows to extend the list, not to rewrite the test.</li>
</ol>
<p>Specific gotchas in our app's stack:</p>
<ul>
<li><strong>SwiftUI inline pickers</strong> render the selected option's text as a regular StaticText in the Settings list. Useful as a "current value" probe, but you must scope your predicate by type — there's also a NavigationBar title and a compound Button label that include the same string.</li>
<li><strong>iOS NavigationStack</strong> sub-screens have their own NavigationBar with a title that matches the section name. <code>name == "Settings"</code> matches the Settings <em>row</em> on Profile AND the Settings <em>NavigationBar</em> one screen deeper. WDA picks one and caches its identity; if the screen transitions before you click, it 404s.</li>
<li><strong>Compose Navigation</strong> bottom-nav tabs are no-ops when you're already in their sub-graph. Tapping the Profile tab from the Settings sub-screen does NOT pop back to Profile root. Use <code>KEYCODE_BACK</code> to pop.</li>
</ul>
<p><img alt="DECIDE-phase identification strategies, log scale (estimated): hardcoded coord 1ms, accessibility-id match 5ms, single text match 10ms, multi-name fallback 15ms, regex over XML 30ms, full tree walk 100ms, OCR over screenshot 400ms." src="/static/blog_fast_mobile_e2e/05_decide.png"/></p>
<pre><code>{
  "chart": "decide_operations",
  "note": "Estimated ranges; DECIDE is rarely the bottleneck. Optimize for stability (acc-id &gt; text-set &gt; coords &gt; OCR) first.",
  "operations": [
    {"name": "hardcoded coordinate (no parse)",      "ms": 1,    "tier": "fast"},
    {"name": "accessibility id match (predicate)",   "ms": 5,    "tier": "fast"},
    {"name": "single text match (known label)",      "ms": 10,   "tier": "fast"},
    {"name": "multi-name fallback set",              "ms": 15,   "tier": "fast"},
    {"name": "regex over XML dump",                  "ms": 30,   "tier": "situational"},
    {"name": "full tree walk + filter",              "ms": 100,  "tier": "situational"},
    {"name": "OCR over screenshot",                  "ms": 400,  "tier": "slow"}
  ]
}
</code></pre>
<hr/>
<h2 id="6-recovery--the-things-that-break">6. RECOVERY — the things that break</h2>
<ul>
<li><strong>WDA dies on app reinstall.</strong> The XCTest runner is attached to the previous build of the bundle. Symptom: <code>curl http://localhost:8100/status</code> returns <code>Connection reset by peer</code>. Recovery: re-run <code>xcodebuild ... WebDriverAgentRunner test</code> and wait for <code>ServerURLHere-&gt;http://...:8100&lt;-ServerURLHere</code>. Takes ~30-60s. Plan reinstall cycles around this.</li>
<li><strong>adb shows the same Pixel twice</strong> when both USB and mDNS-TLS transports come up. <code>adb shell</code> errors with <code>more than one device/emulator</code> (exit 255). Auto-fix: parse <code>adb devices</code>, prefer the USB-style serial (no <code>_adb-tls-connect</code> suffix), set <code>ANDROID_SERIAL</code>, all subprocess calls inherit it.</li>
<li><strong><code>uiautomator dump</code> SIGKILLs (137) during transitions.</strong> Animations confuse the dump. Persistent u2 server doesn't have this problem because it holds a stable handle.</li>
<li><strong><code>am kill</code> is a no-op when the app is foregrounded.</strong> OS judges foreground apps "important" and silently skips. Combined with <code>am start</code> on the same activity (no-op when already foregrounded), your test_relaunch can do nothing. That's fine for "doesn't crash" intent, but verify foreground state, don't assume the kill happened.</li>
<li><strong><code>am force-stop</code> wipes BLE PendingIntent scans.</strong> On our app the rebuild takes 30-50s. Use <code>am kill</code> for "test relaunch hooks", <code>force-stop</code> only when explicitly testing cold-start recovery.</li>
</ul>
<hr/>
<h2 id="7-delays--sleep-is-the-enemy">7. DELAYS — sleep is the enemy</h2>
<p>Default reflex when something flakes: add a sleep. Don't.</p>
<ul>
<li><strong>If you need to wait for a known event, poll for it.</strong> Most "find" helpers already do — write yours that way. Caller code should never <code>sleep</code> before a <code>find</code> (the find polls; pre-sleep is dead time).</li>
<li><strong>If you don't know what you're waiting for, write it down before sleeping.</strong> Comments like <code>time.sleep(1.5)  # let chart re-render</code> aren't decorative — they're a debt that future-you will renegotiate. The honest answer is usually "0.3s is fine".</li>
<li><strong>Cold-start launches really do block.</strong> WDA's <code>POST /wda/apps/launch</code> waits until the app foregrounds. iOS <code>xcrun devicectl device process launch</code> returns when the launch <em>kicks off</em> (~240ms) but not when the app is rendered. Read the framework docs for each entry point so you know which it is.</li>
</ul>
<p>After the sleep audit, our two suites went from 175s+ to ~55s with no reliability cost. About 30% of that was killing dead sleeps; the rest was the snapshot/predicate work above.</p>
<hr/>
<h2 id="8-architecting-for-testability--existing-app-vs-greenfield">8. ARCHITECTING for testability — existing app vs greenfield</h2>
<h3>Greenfield</h3>
<p>If you control the app source from day 1:</p>
<ul>
<li><strong>Add <code>accessibilityIdentifier</code> to every interactive element.</strong> Free at write time, infinitely valuable later. They never rotate, they never get translated, they're invisible to users.</li>
<li><strong>Wire a debug-only deep-link router from the start.</strong> <code>myapp://nav/settings/advanced</code> → <code>navController.navigate(...)</code>. Gated to <code>#if DEBUG</code> / <code>BuildConfig.DEBUG</code>. ~10-15 lines of code. Pays for itself the first time you skip a 5-step manual nav in CI.</li>
<li><strong>Expose state-altering debug intents</strong> — clear cache, set feature flag, jump app to an arbitrary state. Beats trying to reach state X by clicking your way there.</li>
<li><strong>Telemetry-flush on foreground.</strong> If you have an event queue, flush it on every <code>onResume</code>/<code>scenePhase=.active</code>. Tests that assert "this event reached the server" will thank you.</li>
</ul>
<h3>Existing app</h3>
<p>Don't rewrite. Layer on:</p>
<ol>
<li><strong>Predicate-find + element-click</strong> path before adding <code>accessibilityIdentifier</code>s. You don't need source changes.</li>
<li><strong>Add a debug deep-link route for the routes the test suite uses most.</strong> It's surgical. Even just <code>app://nav/settings</code> and <code>app://nav/advanced</code> saves seconds × N tests × M runs/day. Gate to debug builds — production builds don't expose nav routes.</li>
<li><strong>When you DO add accessibility identifiers</strong>, do it test-first. Add the identifier, switch the test to use it, observe the speedup, move on. Don't bulk-annotate.</li>
</ol>
<p>The cost-benefit on a debug deep-link router for an existing app:</p>
<pre><code>launch + nav (WDA tap-and-poll, optimized):  ~5s
launch + deep link:                          ~1.2s

Per-test savings:                            ~3.8s
Code change:                                 ~30 lines, gated to DEBUG
Days to break even (1 CI run/day):           ~1
</code></pre>
<hr/>
<h2 id="9-db--server-verification--talk-directly">9. DB / SERVER VERIFICATION — talk directly</h2>
<p>Most E2E tests need to confirm "the data made it to the backend". Common pattern: shell into the backend, run a query, parse the output. We did this with <code>ssh prod &amp;&amp; python -c "..."</code> for two weeks before realizing:</p>
<ul>
<li>Cold call: ~6.7s (SSH connect + remote Python startup + sqlalchemy import + DB connect)</li>
<li>Warm call: ~0.9s (sqlalchemy still imports per call)</li>
</ul>
<p>Direct connection with a cached engine on the test runner:</p>
<ul>
<li>Cold call: ~1.3s (TLS + auth)</li>
<li><strong>Warm call: ~50-150ms</strong> (engine + connection pool reuse)</li>
</ul>
<p>Most modern managed DBs are publicly addressable — Neon, Supabase, RDS-with-public-endpoint. The URL is the only secret. Stash it in a gitignored file or env var; share the file path across the test suite via a small <code>_get_pg_url()</code> helper that falls back gracefully:</p>
<pre><code>1. POSTGRES_URL env var
2. local cache file (gitignored)
3. one-time fetch (e.g. ssh + grep) that auto-populates the cache
</code></pre>
<p>A cached SQLAlchemy engine at module level keeps the connection pool warm across tests in the same pytest session.</p>
<p><strong>Side benefit we didn't expect</strong>: the SSH path was silently swallowing a "column does not exist" error in one of our queries. Direct path failed loudly the first time. Prefer code paths that fail loudly.</p>
<hr/>
<h2 id="10-latency-budgets--what-to-actually-measure">10. LATENCY BUDGETS — what to actually measure</h2>
<p>Chains of mobile actions are made of:</p>
<table>
<thead>
<tr>
<th>component</th>
<th>typical cost</th>
</tr>
</thead>
<tbody>
<tr><td><code>adb shell input tap</code></td><td>~70ms</td></tr>
<tr><td>WDA <code>/wda/tap</code> (idle wait off)</td><td>~500ms</td></tr>
<tr><td>WDA <code>/wda/tap</code> (idle wait default)</td><td>~1100ms</td></tr>
<tr><td><code>adb shell uiautomator dump</code></td><td>~2200ms</td></tr>
<tr><td>openatx u2 <code>dumpWindowHierarchy</code></td><td>~200ms</td></tr>
<tr><td>WDA <code>/source</code> (sparse screen)</td><td>~1000ms</td></tr>
<tr><td>WDA <code>/source</code> (dense screen)</td><td>~2700ms</td></tr>
<tr><td>WDA <code>POST /element</code> predicate</td><td>~300ms</td></tr>
<tr><td>WDA <code>/screenshot</code></td><td>~600ms</td></tr>
<tr><td><code>adb exec-out screencap -p</code></td><td>~330ms</td></tr>
<tr><td>iOS cold launch (WDA path)</td><td>~2300ms</td></tr>
<tr><td>iOS cold launch (devicectl + url)</td><td>~1100ms</td></tr>
<tr><td>Android cold launch (monkey)</td><td>~1000ms</td></tr>
<tr><td><code>ssh + python -c</code> cold</td><td>~6700ms</td></tr>
<tr><td><code>ssh + python -c</code> warm</td><td>~900ms</td></tr>
<tr><td>Direct managed-DB SQL warm</td><td>~80ms</td></tr>
</tbody>
</table>
<p>When you write a test, sum the components in your head. If your "tap → wait → tap → wait → assert" budget says 3s but the test takes 12s, find the missing 9s. It's almost always either a snapshot you didn't realize you'd triggered, a quiescence wait you didn't disable, or an SSH call you could've avoided.</p>
<p><img alt="Mobile E2E latency budget reference, log scale: a complete sorted list of every operation cost cited above, color-coded green for reach-for, grey for situational, red for avoid in hot paths. Spans 70ms (adb input tap) to 6.7s (ssh cold)." src="/static/blog_fast_mobile_e2e/03_budget.png"/></p>
<pre><code>{
  "chart": "latency_budget",
  "operations": [
    {"name": "adb shell input tap",                  "ms": 70,   "tier": "fast"},
    {"name": "Direct managed-DB SQL (warm)",         "ms": 80,   "tier": "fast"},
    {"name": "openatx u2 dumpWindowHierarchy",       "ms": 200,  "tier": "fast"},
    {"name": "WDA POST /element predicate",          "ms": 300,  "tier": "fast"},
    {"name": "adb exec-out screencap -p",            "ms": 330,  "tier": "situational"},
    {"name": "WDA /wda/tap (idle off)",              "ms": 500,  "tier": "situational"},
    {"name": "WDA /screenshot",                      "ms": 600,  "tier": "situational"},
    {"name": "ssh + python -c (warm)",               "ms": 900,  "tier": "situational"},
    {"name": "WDA /source (sparse screen)",          "ms": 1000, "tier": "situational"},
    {"name": "Android cold launch (monkey)",         "ms": 1000, "tier": "situational"},
    {"name": "iOS cold launch (devicectl)",          "ms": 1100, "tier": "situational"},
    {"name": "WDA /wda/tap (idle default)",          "ms": 1100, "tier": "slow"},
    {"name": "adb shell uiautomator dump",           "ms": 2200, "tier": "slow"},
    {"name": "iOS cold launch (WDA path)",           "ms": 2300, "tier": "slow"},
    {"name": "WDA /source (dense screen)",           "ms": 2700, "tier": "slow"},
    {"name": "ssh + python -c (cold)",               "ms": 6700, "tier": "slow"}
  ]
}
</code></pre>
<hr/>
<h2 id="11-iteration-loop--the-metarule">11. ITERATION LOOP — the meta-rule</h2>
<p>You will write more tests than you realize. The cumulative cost of slow tests across a codebase is shocking. Before optimizing, profile:</p>
<pre><code>phases = []
def step(label, fn):
    t0 = time.perf_counter()
    out = fn()
    phases.append((label, (time.perf_counter() - t0) * 1000))
    return out

step("open session",  driver.open_session)
step("source #1",     lambda: driver.source(sess))
step("tap profile",   lambda: driver.tap(sess, *PROFILE_TAB))
...
print(f"{label:24s} {ms:6.0f}ms")
</code></pre>
<p>This is 20 lines and it tells you exactly where the time went. Far better than guessing. Keep these probe scripts in your test repo — they pay for themselves the second time the suite slows down.</p>
<p>When the suite is fast enough that the wall time is dominated by the bits you can't change (cold launch, BLE protocol, network round trips), stop. The remaining win is in parallel test execution or hardware, not micro-optimization.</p>
<hr/>
<h2 id="12-tldr--defaults-to-set-on-every-project">12. TL;DR — defaults to set on every project</h2>
<p>If you want the bullet-point cheat sheet:</p>
<ul>
<li>WDA session: <code>waitForIdleTimeout: 0</code>, <code>snapshotMaxDepth: 30</code>, <code>shouldUseCompactResponses: true</code>.</li>
<li>uiautomator: install <code>pip install uiautomator2 &amp;&amp; python -m uiautomator2 init</code>, then talk to localhost:9008 via stdlib HTTP.</li>
<li>Find via predicate, not /source. Scope by type to avoid stale-element traps.</li>
<li>Hardcode bottom-nav coords for stable tab bars.</li>
<li>Drop post-tap sleeps when followed by a polling find.</li>
<li>Verify post-action state with a destination-specific marker. <code>pytest.fail</code>, not <code>step_log.warn</code>.</li>
<li>For a test you'll run more than 50 times, add a <code>#if DEBUG</code> deep-link route to the destination screen.</li>
<li>Connect to your DB directly from the test runner. Cache the engine at module level.</li>
<li>Profile your suite with <code>time.perf_counter()</code> blocks before optimizing.</li>
</ul>
<p>The above takes one pass to set up; the speedup compounds for the life of the project.</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/fast_mobile_e2e/</guid>
      <pubDate>Wed, 13 May 2026 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Using flex PCB as electrode mount</title>
      <link>https://andykong.org/blog/fpc-electrode-clip/</link>
      <description>First opaque clear PCBs</description>
      <content:encoded><![CDATA[<html><body><p>Recently I built a wearable EKG circuit with Lucia for a fun party device that visualizes your heart rate.</p>
<p><img alt="" class="addpic" src="/static/fpc-electrode-clip/0.png"/></p>
<p>The most clunky part of any biosensing project is coupling the human to the circuit, and this was no different. We had to get EKG gel electrodes, corresponding snap electrode cables, and then connect them to the person in question. This is annoying because the electrodes are disposable, the wires are too long and windy, and the connection to the board is bulky because the EKG electrodes normally go off-body to get digitized. </p>
<p><img alt="" class="addpic" src="/static/fpc-electrode-clip/1.png"/></p>
<p>It would be much nicer if the EKG was an all-in-one piece that sits across the heart and captured the same signal. A worn EKG like this exists already for monitoring people over 1-2 days, but it isn't fun or hackable or attainable by normal people, so I was looking for alternatives.</p>
<p><img alt="" class="addpic" src="/static/fpc-electrode-clip/2.png"/></p>
<p>One such method used flex PCBs as the "snap", using the flexibility of the FPC material to ensure good contact with an electrode. I could think of many ways to do this coupling but I had two problems: I wasn't sure how big the electrodes were (no calipers) and I could think of 3+ ways to shape the electrode-mounting hole on the FPC itself. I started by modeling out the different geometries for this (simple hole, star-shaped hole, and keyhole) in 10 different sizes spanning 3-4mm (I knew we had a 3.5mm snap electrode, I just wasn't sure which part was 3.5mm). I got this on flex, and just for fun I ordered it on JLC's clear PCB. </p>
<p><img alt="" class="addpic" src="/static/fpc-electrode-clip/3.png"/></p>
<p>They emailed me immediately asking about soldermask, and since I wanted the gold to be exposed for good contact, I told them to remove all of it. As it turns out, the base material of the clear FPC is clear (and slightly pink?), but the surface finish is not smooth enough to see through it without soldermask. You can approximate the soldermask with a piece of clear tape on both sides.</p>
<p><img alt="" class="addpic" src="/static/fpc-electrode-clip/4.png"/></p>
<p>As for the electrodes, it turns out just pretending the buckle was perfectly 3.5mm is a decent strategy. The simple hole performed ok, the star-hole had a bit too much material in the middle, and the keyhole worked just as well as the simple hole (but needed smaller sizes).</p>
<p><img alt="" class="addpic" src="/static/fpc-electrode-clip/5.png"/></p>
<p>It's easy to say in hindsight, but the simple holes used the flexibility of the metal, which deforms and does not snap back. This meant repeated use of a single hole meant it stopped making good contact (though it still held on to the electrode well enough). </p>
<p><img alt="" class="addpic" src="/static/fpc-electrode-clip/6.png"/></p>
<p>The keyhole intermediate channel was 2mm, and held much more consistently across uses. I think this is because it relied on the flexibility of the underlying substrate, or on the flexing as opposed to the deformation of the thin metal layer. In the future we'll go with the too-thin channel, and I think the electrode clips and EKG circuit will be able to all fit on the same circuit. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/fpc-electrode-clip/</guid>
      <pubDate>Tue, 03 Feb 2026 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Teardown of the Urbanista Los Angeles headphones</title>
      <link>https://andykong.org/blog/urbanista-teardown/</link>
      <description>Removing another self-powering device from the world</description>
      <content:encoded><![CDATA[<html><body><p>Hello all, it's certainly been a while since my last post so I thought we'd do something fun. Because of my current startup, I'm really interested in consumer electronics which can power themselves. I've only found a few devices like this, and today we're gonna take one apart to see how they're implementing it.</p>
<h1 id="background">Background</h1>
<p>We're currently in a battery-powered era of devices, driven by the cheap availability of rechargable, high density lithium batteries. However, as the chips powering these devices get more efficient, environmental energy sources like light or heat become a large enough source of power to contribute meaningful amounts of power to the device's battery life. Today, electronics that add energy harvesting can 2-3x battery life over pure battery-operated ones, especially if the duty cycle is low. </p>
<p>The development of light-harvesting products specifically is driven by the availablility of flexible, "normal"-looking PowerFoyle solar cells by a Swedish company called Exeger. Light-harvesting electronics require a minimum power (light level) to harvest properly, and the minimum light level is higher the smaller the photovoltaic cell. PowerFoyle's cells are optimized for indoor harvesting so even dim indoor lighting can make it work, and the consumer device with the most area turns out to be headphones.</p>
<p><img alt="" class="addpic" src="/static/urbanista-teardown/0.png"/></p>
<p>Since we all work in the same space, I felt compelled to get one and test their panels myself. Exeger has partnered with a lot of big brands like JBL and Adidas, but the cheapest device I could find on eBay was a refurbed/stolen pair of Urbanista Los Angeles headphones.</p>
<p><img alt="" class="addpic" src="/static/urbanista-teardown/1.png"/></p>
<p>My interests lie mostly in the PV cell and the harvesting methodology, but I'm always interested to see how people make certain mechanisms at scale. The rest of this post will mostly be pictures.</p>
<h1 id="teardown">Teardown</h1>
<p>Here's the device</p>
<p><img alt="" class="addpic" src="/static/urbanista-teardown/2.png"/></p>
<p>Here's the panel we're after, built into the top band</p>
<p><img alt="" class="addpic" src="/static/urbanista-teardown/3.png"/></p>
<p>We start by popping off the pieces of the device that are snap-fit — The inner band, parts of the outer band, the earmuffs. You can immediately see they've strain reliefed the cable on the headband</p>
<p><img alt="" class="addpic" src="/static/urbanista-teardown/4.png"/></p>
<p>There's a couple of stickers keeping the band together, once those go the PV cell just falls out </p>
<p><img alt="" class="addpic" src="/static/urbanista-teardown/5.png"/></p>
<p><img alt="" class="addpic" src="/static/urbanista-teardown/6.png"/></p>
<p>I used a clamp to pop off the two outer shell bits on the headphone themselves. The right one contains the harvesting and bluetooth chips (labeled on paper), and 2 mics for the noise-cancelling. </p>
<p><img alt="" class="addpic" src="/static/urbanista-teardown/7.png"/></p>
<p>The left earmuff has a 750mAh battery (10 kJ), PMIC, and 1 mic. I roughly calculated charging rate in direct sunlight and came up with 3-4 hrs to go from 0 to 100%.</p>
<p><img alt="" class="addpic" src="/static/urbanista-teardown/8.png"/></p>
<p>The part of the band coupling to the earmuffs (for the wire pass-through) is made of steel, which I found a bit surprising — its the only mechanical component that's made of metal in the whole device. Actually I felt a bit bad seeing how good the build quality was during the teardown. It was taken apart in a way that could be put back together, but I'm not really interested. </p>
<h1 id="analysis">Analysis</h1>
<p>From my brief research into this, Exeger bought another company called Sunboost which was also nordic in origin, and this is the harvester chip that's labeled SUNBST. Exeger also likes to partner with e-peas, a Belgian chip company. For now I don't have more info, so I'll just leave this section a stub. </p>
<h1 id="conclusion">Conclusion</h1>
<p>Two interesting realizations I had during this:</p>
<ol>
<li>
<p>The amount of power available in the environment (heat/light) has been fixed for millenia, but our devices have become steadily more efficient. The next generation of devices will not need charging, the one after that will not even need energy harvesting.</p>
</li>
<li>
<p>All devices trend smaller over time, except for things that must conform to the human body (headphones, gloves, bracelets). All tools are made small to the extent that people can still manipulate and use them effectively.</p>
</li>
</ol>
<p>My review of the Urbanista Los Angeles headphones: Excellent build quality, decent noise cancelling, slightly uncomfortable and cheap earmuffs. Panel testing will come in a later</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/urbanista-teardown/</guid>
      <pubDate>Sun, 01 Feb 2026 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>First lessons from ordering CNC parts from China</title>
      <link>https://andykong.org/blog/cnctips1/</link>
      <description>metal on tap</description>
      <content:encoded><![CDATA[<html><body><p>I'm working on Chargerless, a minimal, self-powered health tracker for the wrist. In this post I'm gonna share some tacit knowledge I've acquired from doing several CNC orders</p>
<p>The Chargerless device is so small that the case needs to be super sturdy to tolerate daily stresses without popping out the spring bars that hold the band, so I've decided to make the enclosures out of metal. At first I went through a NYC company that does small batch investment casting for the enclosures (resin printed pos -&gt; neg plaster mold -&gt; pour brass/silver to make a final piece), but the gate cutting and polish/buffing is pretty expensive (measured in either time or money). I wasn't able to find Chinese shops to do small batch casting + finishing (MOQs usually around 300-1000 pc), so instead I started looking at Chinese CNC. </p>
<p>I'd tried JLC or PCBWay's CNC services in the past, but for my model they would always charge an unreasonable price — for first prototypes this was fine, but later on I needed to be able to get a bunch of them. I took 3-4 suggestions for other CNC shops from friends and either got connected over email or just filled out the contact form. After a few emails back and forth, I would send my 3D model and the quantity, materials, postprocessing, and finishes that I wanted. After half a day they'd send back a quote (excel spreadsheet), I would sign and pay and send it back. 9 days later they'd say "hey your parts are out the door, thanks for doing business" and then I'd wait eagerly for the parts to arrive.</p>
<p>The main things I've learned from this are as follows:</p>
<ul>
<li>
<p>You just need to send a model, no drawing is necessary unless you have a critical dimension. </p>
</li>
<li>
<p>Aluminum is cheap, Stainless steel is expensiver, and Titanium is really expensive, followed ratios of roughly 1:2:3 in my experience</p>
</li>
<li>
<p>There's only like 5 surface finishes, and since they're portable between shops you need to use them when asking for quotes. Listed in order of increasing smoothness: matte, satin, as-finished, glossy, and mirror-finish — the first few are done by sand or bead-blasting, the latter two are done by hand so are pretty expensive.</p>
</li>
<li>
<p>CNC shops mess up sometimes, so the first job with a new vendor shouldn't be a huge order. My first model there were visible tool marks on flat surfaces, and certain holes were deburred way too much. By taking good pictures and sending them back to the agent, these problems were fixed in future orders. If you've gotten a model CNC'd before and had to correct things with the vendor, when you go to another shop send a little slideshow of the errors of past vendors just so they know what to avoid</p>
</li>
<li>
<p>Price breaks happen around 10 and 100, you can feel free to ask for a quote with 10pc and 100pc of the same model just to see how future costs will scale</p>
</li>
<li>
<p>Just like there are electronic parts where you don't care about where its from (jellybean components), CNC is a jellybean operation. If your prices are too high, get your model quoted elsewhere, nobody will mind</p>
</li>
<li>
<p>Finishes are metal-specific: aluminum gets anodization (any color), steel gets PVD (any color), powder coat (any color), Cerakote (any color), chrome plate (silvery), blackened (black). Just know your options</p>
</li>
<li>
<p>Anodization is a great way to color aluminum parts, but has its downsides. It can leave spots in surfaces, and it definitely will be a slightly different color each batch. But it is nonconductive</p>
</li>
</ul>
<p>Ok that's all, just words this post! Cya around</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/cnctips1/</guid>
      <pubDate>Thu, 04 Dec 2025 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Running QUCS Studio (uSimmics) on a Mac</title>
      <link>https://andykong.org/blog/qucsstudioinstall/</link>
      <description>RF circuit simulation without ANY piracy!</description>
      <content:encoded><![CDATA[<html><body><p>Hi, if you're reading this you must be truly desperate — casual readers would never ever click a blog post titled like this. Today I'm gonna walk you through running QUCS Studio on your Mac computer. </p>
<p><img alt="" class="addpic" src="/static/qucsstudioinstall/0.jpg"/></p>
<h1 id="background">Background</h1>
<p>Software people love Apple, but real engineers use software that only runs on Windows. As an aspiring EE, I can usually get by for circuit/SPICE simulation via Paul Falstad's lovely <a href="https://www.falstad.com/circuit/circuitjs.html">CircuitJS</a> web calculator or LTSpice.app, but as I got into more high frequency stuff, there were rapidly fewer options. While Windows users get to pick from Microwave Office, Pathwave ADS, HFSS, or Cadence, Apple users get nothing at all. This software is big, bulky, and running it in a VM is painful. Luckily there are some smaller packages which can do the same simulation, such as QUCS-Studio (which looks very familiar for ADS users), but again, annoying to run in a VM. If not a VM, you might think of Wine for running an .exe, to which I can respond I've never ever gotten that to work for anything.</p>
<p>However, recently I found a guide by Vanderson PC (<a href="https://www.vandersonpc.com/Qucstudio-on-MacOs/">link</a>, <a href="https://archive.is/SYlBm">mirror</a>) on running QUCS-Studio using PlayOnMac. I followed it diligently and it did not work either. This is because the guide is from 2017 and there have been a lot of MacOS breaking changes in the meantime, and PlayOnMac has not been so updated. However, the problems were solvable and I will share how.</p>
<h1 id="1-start-the-guide">1. start the guide</h1>
<p>Ok just do everything the guide says. Yes uSimmics 5.8 works even though the blog author is running QUCS-Studio 3.3.4</p>
<h1 id="2-follow-the-guide-until-step-6">2. follow the guide until Step 6</h1>
<p>On step 6, you are asked to select a Wine version. I don't have a wine version. The first step of this guide should be how to install a wine version on PlayOnMac.</p>
<p><img alt="" class="addpic" src="/static/qucsstudioinstall/1.png"/></p>
<p>Leave the configuration menu. Install a Wine version by going to Tools-&gt;Manage Wine versions. </p>
<p><img alt="" class="addpic" src="/static/qucsstudioinstall/2.png"/></p>
<p>If you're like me, the list on the left is empty. Pretty daunting. If it's still empty after 5s you have a problem.</p>
<p><img alt="" class="addpic" src="/static/qucsstudioinstall/3.png"/></p>
<p>After reading some <a href="https://www.playonmac.com/en/topic-17045-2.html">forums</a>, I realized that PlayOnMac makes some questionable code decisions. One of which is that all the URL requests have a 5s timeout and then fail silently. To figure out where it's failing, you exit PlayOnMac (Cmd+Q), open up a terminal and navigate to the .app file, and then run the application file from the terminal so you can see the debug messages (Find PlayOnMac.app, then run <code>./PlayOnMac.app/Contents/MacOS/playonmac</code>). It should boot normally. </p>
<p><img alt="" class="addpic" src="/static/qucsstudioinstall/4.png"/></p>
<p>Now go to the Tool-&gt;Wine window again. If the list stays empty, you should see something like this:</p>
<p><img alt="" class="addpic" src="/static/qucsstudioinstall/5.png"/></p>
<p>So, you can check that the "phoenicis" URL is still active by just going to it using your browser. If it's still up, it just means that their server is slow and the request is just timing out locally. Go into the "WineVersionsFetcher.py" file and change the timeout to like 15 or 30 or 60 seconds. Then you'll get your Wine version downloaded.</p>
<p><img alt="" class="addpic" src="/static/qucsstudioinstall/6.png"/></p>
<p>After that continue following the guide. Other people on the forums had an issue where Wine wouldn't download properly, but you could download it yourself and move it into the directory it was supposed to be in and it'd work. Honestly I feel a bit like a battle medic when I'm doing edits like this, but y'know, it's gotta work. </p>
<h1 id="step-3-finish-the-guide">Step 3. finish the guide.</h1>
<p>Just follow the other guy's post, it's a good blog. No step should throw an error. </p>
<p><img alt="" class="addpic" src="/static/qucsstudioinstall/7.png"/></p>
<p>Sorry for the lazy post, have fun simulating!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/qucsstudioinstall/</guid>
      <pubDate>Sat, 31 May 2025 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Bringing up a custom RP2040 board</title>
      <link>https://andykong.org/blog/rp2040_bringup/</link>
      <description>First time lessons are easily forgotten, here mine are persisted</description>
      <content:encoded><![CDATA[<html><body><p>Hello, today I'm gonna walk you through putting the RP2040 on a custom PCB and programming it. I'm used to Arduino but chose to use picotools for this — but I'm getting ahead of myself. Target audience is Arduino script kiddies. </p>
<p class="caption"> The screen that's in your future </p>
<p><img alt="The screen that's in your future" class="addpic" src="/static/rp2040_bringup/0.png"/></p>
<div class="toc"><h2 id="table-of-contents" style="margin:0px;">Table of Contents</h2>1. <a href="#whats-this-for">What's this for</a><br/>2. <a href="#hardware">Hardware</a><br/>   2.1 <a href="#schematic">Schematic</a><br/>   2.2 <a href="#layout">Layout</a><br/>3. <a href="#embedded--software">Embedded / Software</a><br/>   3.1 <a href="#method-1-download-one">Method 1: download one</a><br/>   3.2 <a href="#method-2-arduino-ide">Method 2: Arduino IDE</a><br/>   3.3 <a href="#method-3-build-it-yourself">Method 3: Build it yourself</a><br/>4. <a href="#warning-i2cspiuartpwm-pins-are-baked-into-hardware-pio-isnt-but-parallel-reads-should-be-adjacent-pins">Warning: I2C/SPI/UART/PWM pins are baked into hardware. PIO isn't, but parallel reads should be adjacent pins</a><br/>5. <a href="#conclusion">Conclusion</a><br/></div>
<hr/>
<h1 id="whats-this-for">What's this for</h1>
<p>Ok so sometimes you want to make your circuit small, or just well-integrated, and a messy breadboard takes up a lot of space and decreases your investability (Sometimes it increases it, see <a href="https://orbit.engineering/">Orbit's blog</a> for an example). In this situation you want to put the microcontroller on a custom PCB. Then programming gets complicated — the USB datastream from Arduino can't upload directly to most ICs, requiring a translator chip (USB-&gt;UART) to convert it, which is another chip to figure out. Or maybe cost is an issue. The RP2040 hits both targets, costing ~70c (as of 2025) and allowing direct USB input for programming. </p>
<p><img alt="" class="addpic" src="/static/rp2040_bringup/1.png"/></p>
<h1 id="hardware">Hardware</h1>
<h2 id="schematic">Schematic</h2>
<p>First step is the schematic and layout. Most manufacturers publish a minimal set of components needed to run their chip, and RP2040 is no different. The schematic can be found <a href="http://www.technoblogy.com/show?3U75">on this blog</a> and I've ported the picture for posterity. </p>
<p class="caption"> Minimal schematic for RP2040 </p>
<p><img alt="Minimal schematic for RP2040" class="addpic" src="/static/rp2040_bringup/2.png"/></p>
<p>None of the parts are really special. The power converter is needed to change 5V into 3.3V (max VIN is like 3.6V I think?), anad then the D+ and D- lines from the USB which carry the programming require in-line resistors to (I think) incur a voltage drop so they don't break the input pins (5V signals directly might wreck those pins). The flash chip they use is required (no on-chip flash like Arduino or others), but is pretty cheap and stocked by most PCB shops. </p>
<p class="caption"> You'll want a BOOTSEL button, ask me how I know </p>
<p><img alt="You'll want a BOOTSEL button, ask me how I know" class="addpic" src="/static/rp2040_bringup/3.jpg"/></p>
<p>This board really is the minimal set, but there's a few more bits I'd add to make your life easier. For example, if BOOT is connected to GND on startup, the RP2040 goes into BOOTSEL mode, which you need in order to program it IF you also want to get debug messages over USB. So you should probably add a pushbutton bridging BOOT and GND. You might also want to do this for the RESET pin. Otherwise you'll have to do this with a paperclip or a pen spring.</p>
<p>I'd also add an LED + resistor from the 3.3V supply, just so we can tell when the thing is on or not, and then another LED + resistor wired to an unused GPIO so we can use a Blink sketch to check if our uploads are working.</p>
<h2 id="layout">Layout</h2>
<p><img alt="" class="addpic" src="/static/rp2040_bringup/4.png"/></p>
<p>There's a lot of recommendations on the datasheet for where to place everything, but really it comes down to priority. IMO everything should stay on the same plane as long as possible. High-speed stuff (crystal) needs to be quite close, and decoupling caps shouldn't be much further. For the flash and other sensors, proximity won't matter much.</p>
<h1 id="embedded--software">Embedded / Software</h1>
<p class="caption"> My board </p>
<p><img alt="My board" class="addpic" src="/static/rp2040_bringup/5.jpg"/></p>
<p>When you first plug it in, the RP2040 will not show up as a progammable device port in the Arduino IDE, instead your computer will beep and tell you about the new USB flash drive you just plugged in. The USB device that shows up is the RP2040 in BOOTSEL mode, which by default exposes itself as a USB stick which you can program via drag n' drop with a .uf2 file. The IC saves the program to flash and unplugs itself (in software). After you upload a valid uf2, the RP2040 stops showing up as a USB mass storage device, and if you want to upload another UF2 (instead of through the IDE) you'll need to power cycle it while holding down the BOOTSEL button. </p>
<p>If it doesn't show up as a USB device, that sucks (you may have forgotten to route the power (I did this), just probe stuff and look at the schematic until it makes sense. </p>
<p class="caption"> from [here](https://th.cytron.io/tutorial/setting-up-maker-uno-rp2040-arduino) </p>
<p><img alt="from here" class="addpic" src="/static/rp2040_bringup/6.png"/></p>
<p>Now you might be thinking to yourself, where do I get a .uf2 file? Ok so three ways I know of. </p>
<h2 id="method-1-download-one">Method 1: download one</h2>
<p>There's some basic "check functionality" sketches in the <a href="https://github.com/raspberrypi/pico-examples">raspberrypi/pico-examples github repo</a> which you can download and then upload directly. You can't edit these, but they let you check that the IC is soldered properly. </p>
<p><img alt="" class="addpic" src="/static/rp2040_bringup/7.png"/></p>
<h2 id="method-2-arduino-ide">Method 2: Arduino IDE</h2>
<p>Arduino IDE can actually generate uf2 files, you just have to go to Sketch and hit "Export compiled Binary". This will compile the program for the selected target board (Make sure you're on Tools -&gt; Board -&gt; Raspberry Pi RP2040 Boards -&gt; Generic RP2040) and saves it to the sketch folder. Just to to the sketch folder and drag+drop your uf2. </p>
<p class="caption"> Export compiled Binary option exports as uf2 </p>
<p><img alt="Export compiled Binary option exports as uf2" class="addpic" src="/static/rp2040_bringup/8.png"/></p>
<p><img alt="" class="addpic" src="/static/rp2040_bringup/9.png"/></p>
<h2 id="method-3-build-it-yourself">Method 3: Build it yourself</h2>
<p>Here I must admit I never really learned embedded in school, so my understanding of this is patchy. Basically you write your program in .c and .h files and compile them using Cmake + make. You 'include' libraries by telling <code>CMakeLists.txt</code> to link them, which CMake will pull from the pico-sdk (you need to tell CMake where this is too), at which point your programs will know where they are. Then all this gets compiled together into a binary .elf or .bin or .uf2 file by make. So the steps are, roughly:</p>
<ol>
<li>
<p>Install + cmake + build the <a href="https://github.com/raspberrypi/pico-sdk">pico-sdk</a>, reference it in your <code>.bash_rc</code> or whatever</p>
</li>
<li>
<p>Make .c and .h files however you want, including the libraries you need to use at the top (same format as Arduino, <code>#include &lt;stdio.h&gt;</code>)</p>
</li>
<li>
<p>Add your used libraries into the <code>CMakeLists.txt</code> file under <code>target_link_libraries(${PROJECT_NAME}</code></p>
</li>
<li>
<p>Make a <code>build/</code> directory, go into it, run <code>cmake ..</code> and then <code>make</code>, there should be no errors, and if there are, you should fix them.</p>
</li>
<li>
<p>To program the RP2040, you can either 1) upload the output <code>.uf2</code> file to the RP2040 mass storage device by drag n' drop or 2) to flash from the command line, install <a href="https://github.com/raspberrypi/picotool">picotool</a>, make sure the board is visible by running <code>picotool info</code>, then flashing using <code>picotool load &lt;blah.elf&gt;</code> if your RP2040 is in BOOTSEL (still a USB mass storage device) or <code>picotool load &lt;blah.elf&gt; -f</code> if the RP2040 isn't showing up as a USB device anymore. </p>
</li>
</ol>
<p>There's a minimal example for how to do all this on Github at <a href="https://github.com/oguzbilgic/pico-minimal-build/tree/master">oguzbilgic/pico-minimal-build</a>, and ChatGPT is pretty helpful for debugging. I learned a lot from figuring out this part, but I needed to do this to run someone else's example program — it might not be worth if you don't have this constraint and will only do simple stuff. </p>
<h1 id="warning-i2cspiuartpwm-pins-are-baked-into-hardware-pio-isnt-but-parallel-reads-should-be-adjacent-pins">Warning: I2C/SPI/UART/PWM pins are baked into hardware. PIO isn't, but parallel reads should be adjacent pins</h1>
<p>If you want to use the native I2C/SPI/UART functions, you should know that certain pins are destined to be SCL/SDA and not both. And if you use PWM, make sure your PWM outputs don't share a PWM object + channel (e.g. if two pins are PWM0 A, you will only be able to PWM from one at a time). Just check the datasheet, it'll save you a rev.</p>
<p>PIO is out of the scope of this post, but is mappable to any pin. However, if you need to do a parallel read of 8 channels (like for a camera MIPI), the channels should be arranged in-order going to sequential in-order GPIOs. </p>
<p><img alt="" class="addpic" src="/static/rp2040_bringup/10.png"/></p>
<h1 id="conclusion">Conclusion</h1>
<p>Ok, relatively painless. Simple schematic, layout, and pretty straightforward embedded. RP2040 is a chip is nice, not particularly low-power, but lots of GPIO, communication channels, ADCs, and it's really cheap. But I'll let you know what I think in a few weeks once I've used it more. </p>
<p>Cya!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/rp2040_bringup/</guid>
      <pubDate>Wed, 21 May 2025 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Free custom email domain through Gmail</title>
      <link>https://andykong.org/blog/freebusinessemail/</link>
      <description>1 CRAZY trick that Big Custom Mailbox doesn't want you to know</description>
      <content:encoded><![CDATA[<html><body><p>Ok, so you have a CRAZY idea for a startup that you can't believe nobody's done yet, you incorporated a Delaware LLC via Stripe Atlas and bought a domain, then (vibe)coded  a dope 1-page website so the money tree investors and customers can come and find you. Ahh, life is good. Oh wait, but you can't put your <name dope="" i="" in="" middle="" school="" thought="" was="">@gmail.com address in the contact, or else people will know you're just a kid. And all the alternatives cost something every month, which really cuts into your ARR. What now?? </name></p>
<p>So if you can receive email at a custom domain (e.g. Namecheap offers free custom domain email forwarding) but can't send it, you can solve this. Gmail offers this service for free but has utterly gimped the setup guides, probably just to sell more Google Workspace. I'm gonna tell you how to do it because it's actually really simple. </p>
<h2 id="1-first-navigate-to-gmail-then-hit-settings-then-hit-all-settings-then-go-to-accounts-and-imports">1. First navigate to Gmail, then hit settings, then hit all settings, then go to "Accounts and Imports"</h2>
<p>Menu should look like this. Hit "Add another email address" to trigger a popup</p>
<p><img alt="" class="addpic" src="/static/freebusinessemail/0.png"/></p>
<h2 id="2-put-in-your-custom-email">2. Put in your custom email</h2>
<p><img alt="" class="addpic" src="/static/freebusinessemail/1.png"/></p>
<h2 id="3-fill-out-smtp">3. Fill out SMTP</h2>
<p>Use "smtp.gmail.com" and port 587, then add your current gmail address as the username (remove the @gmail.com). For password you'll need to make an app password. </p>
<p><img alt="" class="addpic" src="/static/freebusinessemail/2.png"/></p>
<p>An app password is basically a way to give an app access to your email without exposing your password. To make one, go to <a href="https://myaccount.google.com/">myaccount.google.com</a>, search for "App passwords" (you will not find it as a menu item), do a sign-in again, then make up a name and generate one.</p>
<p><img alt="" class="addpic" src="/static/freebusinessemail/3.png"/></p>
<p>Copy that into the SMTP popup and hit Add Account:</p>
<p><img alt="" class="addpic" src="/static/freebusinessemail/4.png"/></p>
<p>You are pretty much done. They will send a confirmation email to your custom domain, which, if your forwarding was really set up right, will go somewhere you can click it. From then on you can send emails from your custom domain fo free. Enjoy!</p>
<p><img alt="" class="addpic" src="/static/freebusinessemail/5.png"/></p>
<hr/>
<p>Guide mostly cribbed from the super-thorough but deleted Google Support guide backed up on wayback <a href="https://web.archive.org/web/20230326042630/https://support.google.com/domains/answer/9437157">here</a>. Why do they insist on making their products annoying to use? </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/freebusinessemail/</guid>
      <pubDate>Fri, 16 May 2025 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Let the foot slip</title>
      <link>https://andykong.org/blog/footslip/</link>
      <description>climbing is life</description>
      <content:encoded><![CDATA[<html><body><p>I want to preface by saying I feel stupid writing life lessons. I haven't lived very long, and anything I can write about seems obvious to me (of course it would). And also I cannot live your life for you — I didn't understand most advice until I was in a place to be able to give it myself i.e. I usually did not follow it until too late, learning the hard way. Oh well. </p>
<p>Anyway, I've been rock climbing for about 6 months now. The last time I did something physical consistently was some lifting in high school, so it's been about 6 years. It's really exciting learning what your body can do. I can do four pullups when I could do none before, I can climb a few V4s, someone recently asked me to show them a route, etc. Socrates was right.</p>
<p>Something I noticed is that a lot of climbing is about trusting your own body. "trust the feet" to hold onto a tiny ledge, believing in your own ability to maintain a one-handed hold for half a second so you can get the next hold. This is fine and all, but what really surprised me is how to build this trust. Trust is not declarative like "I pronounce you man and wife", it can only be conveyed by testing it, finding the boundary. On many climbing setups, I felt like my feet were not gonna hold, but I realized that I'm careful on average, so it's about 50-50 when I think I can't hold it. Most of the time I think I'll fall, I just try it anyway and often I'll finish a tricky climb. </p>
<p>The other side is people who never fall. Sometimes I'm watching someone, cheering them on internally; they reach a precipice, look around and up and down, fingers release and they jump off thinking they could never have made it. Sometimes they're right. Sometimes they're old, and know they can't afford to slip. But they will not grow. This is the cost. </p>
<p class="caption"> Anyone else feel like this is the equivalent of a hamster wheel for humans? Just me?? </p>
<p><img alt="Anyone else feel like this is the equivalent of a hamster wheel for humans? Just me??" class="addpic" src="/static/footslip.jpg"/></p>
<p>Lots of real things act like this. <a href="https://guzey.com/abolish-the-nih/">Alexey</a> rails against the NIH for only funding sure-thing research, and I agree; it makes for boring science when everyone knows the results before the work is done, wasting effort, money and time creating ZERO entropy. Research questions should be kinda 50-50 on whether they'll work— at least the results will be interesting. Realizing you can ask for stuff is like this too — at least some of your asks should be rejected. You should aim to miss ~10% of your flights to minimize airport time. You will not improve your ELO by beating up kids (at chess) or make money by taking sure bets. Returns are made of risk.</p>
<p>Another advantage of letting the foot slip is that you learn how to fall. Falling is inevitable, and practicing unintentional falling allows you to recover gracefully. If you miss a flight, they'll often rebook you for free (even though it's your fault??). A failed experiment generates results which can be another piece of a bigger puzzle. A rejected ask is still a connection, and shows you a way to get better. A loss in chess lets you review your weaknesses, or learn their strengths. An attempt to write life advice that spells out too many details when the reader can infer for themselves the point of "learning to fall" applied to the rest of the examples can still help the readers who can't figure how that applies.</p>
<p>Ok sorry this is too long, you probably get it. Climbing is life, so the lessons are the same. Gotta figure out where the feet slip to figure out when they won't. And gotta figure out when YOU believe the feet are gonna slip and when they aren't, and when you're right and wrong, to figure out when to just send it anyway.</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/footslip/</guid>
      <pubDate>Mon, 30 Dec 2024 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>I've been thinking about conviction</title>
      <link>https://andykong.org/blog/conviction/</link>
      <description><![CDATA[<html><body><p>I could clump my friends into four groups:</p>
<h1 id="stable-unsure-pleading">Stable, unsure: pleading</h1>
<p>Nice jobs which let them stay home 3 days a week. Whenever we talk it’s their boss’s kid’s birthday. Free mangoes in the office. I watched Queen’s Gambit and started playing Lichess. I thought there would be more than this.</p>
<h1 id="unstable-unsure-worried">Unstable, unsure: worried</h1>
<p>Making just enough to eke out a life’s work that’s fun to talk about at parties. Always at a sweet 6 month gig doing exactly what they want, wide open road afterwards. I should get a job. I was thinking of going back to school. What am I doing with my life? </p>
<h1 id="stable-sure-steady">Stable, sure: steady</h1>
<p>Family people with a long lease. The days are the same, each one a joy. You catch up but nothing’s changed. Dog’s at the vet again for eating marbles. We’re rewatching House. We’re making dinner with zucchini from the weekend farmer’s market. </p>
<h1 id="unstable-sure-pursuing">Unstable, sure: pursuing</h1>
<p>Risking and failing, doing their work with a smile. Independence, installations and workshops, but also grant rejections, rent increases, food stamps. I’ll be at the VR conference next month. Oh yea, I work for Nintendo at the moment. I’ve been exploring wax as a sculptural medium.</p>
<p><img alt="" class="addpic" src="/static/convictionmeme.png"/></p>
<p>This is an incomplete lens, but it is possible to analyze your life through it, figure out where you are and where you want to be. And if you aren’t sure yet, I think everyone gets there when they’re older, or comes to terms with their inability to change anymore. Anyway, who am I to say? I am only 25.</p></body></html>]]></description>
      <guid isPermaLink="true">https://andykong.org/blog/conviction/</guid>
      <pubDate>Sat, 26 Oct 2024 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Electronics Teardown: Stelo CGM</title>
      <link>https://andykong.org/blog/cgmteardown1/</link>
      <description>Power testing a consumer continuous glucose monitor</description>
      <content:encoded><![CDATA[<html><body><p>Hello everyone, </p>
<p>Hope everything is well in your life. I'm working on my implants talk for Hackaday Supercon (more info <a href="https://hackaday.com/2024/09/17/2024-hackaday-superconference-speakers-round-one/">here</a>). As part of my research, I tried out the Stelo CGM by Dexcom, this is (I think) the first over-the-counter continuous glucose monitor. I'll tell you how it was and then we're gonna dissect this bad boy!</p>
<p><img alt="" class="addpic" src="/static/cgmteardown1/0.jpg"/></p>
<h1 id="how-did-it-feel">How did it feel?</h1>
<p>In a world where glucose monitors are &gt;100$ on Amazon sans insurance, Dexcom offers an affordable ($50) CGM with easily accessible data export, sampling your blood sugar every 5m and passing it to your phone via Bluetooth. The device lasts 15.5 days and comes in a kit of two, meaning annual glucose tracking can now be accomplished for ~1k USD.</p>
<p><img alt="" class="addpic" src="/static/cgmteardown1/1.jpeg"/></p>
<p>Deploying is done through this spring-loaded applicator, and is easy as pressing a button. A sharp, stiff needle in the cap punches a small hole in your arm and retracts, leaving behind the sensor body and a flexible needle with glucose oxidase coating. 15 days later, the app sends you an alert to replace it, and 12 hours after that it stops recording data. </p>
<p>When I got this notification I went and got my teardown tools ready — nothing excites me more than opening a black box!</p>
<h1 id="internal-pics">Internal pics</h1>
<p><img alt="" class="addpic" src="/static/cgmteardown1/2.jpeg"/></p>
<p>I started out using a Dremel, but then realized the soft rubber casing is weak enough you can just use wire snippers. The board is quite thin and liable to break as you peel the rubber off, so I had to be careful. </p>
<p><img alt="" class="addpic" src="/static/cgmteardown1/3.jpeg"/></p>
<p><img alt="" class="addpic" src="/static/cgmteardown1/4.jpeg"/></p>
<p><img alt="" class="addpic" src="/static/cgmteardown1/5.jpeg"/></p>
<h1 id="broad-architecture">Broad architecture</h1>
<p>As far as I can tell, the glucose oxidase on the needle reacts with interstital glucose levels, turning your glucose concentration into a voltage. The Stelo uses an nRF52832 microcontroller to record this, waking up every 30 seconds to read the sensor and every 5 minutes to transmit the data to your phone/watch. The whole thing is powered by a coin cell battery, the Maxell CR1216 (<a href="https://www.maxell.com.tw/images/uploads/2014/10/CR1216_DataSheet_e.pdf">datasheet</a>), which claims a 25mAh capacity</p>
<h2 id="question-1--are-we-getting-scammed-on-lifetime">Question 1 — are we getting scammed on lifetime?</h2>
<p>One of the things I wanted to find out through this teardown was if the battery life was longer than the software claimed. 15 days is an incredibly square number, which led me to believe that Dexcom is "guaranteeing performance" by imposing an artificial software lifetime limit when the sensor could really go for longer.</p>
<p><img alt="" class="addpic" src="/static/cgmteardown1/6.jpeg"/></p>
<p>Immediately after I took the sensor off, the battery still reads 2.95V, but since this battery sports an extraordinarily flat discharge curve I have little idea how much capacity is left. </p>
<p><img alt="" class="addpic" src="/static/cgmteardown1/7.png"/></p>
<p>Since we can't tell by looking at the voltage, I powered the device using an external meter (Nordic PPK2) to find out how much power it draws. Here's the current consumption over 15 minutes:</p>
<p><img alt="" class="addpic" src="/static/cgmteardown1/8.png"/></p>
<p>After an initially high power boot for ~2 minutes, we reach steady operating conditions. Small spikes happen every 7.5 seconds, medium spikes every 30 seconds, and then large bursts of activity every 5 minutes denote Bluetooth activity (large bars on the ends). Average power consumption during steady-state is 8.7uA. If you'd like to see this power consumption data more granularly, feel free to email me for it. </p>
<p><img alt="" class="addpic" src="/static/cgmteardown1/9.png"/></p>
<p>Given its capacity of 25mAh, the battery on the Stelo could theoretically run it for 17 weeks continuously. However, nominal capacity assumes discharge down to 2V — while the nRF52832 microcontroller keeps working down to 1.7V, the other chips on this board may not. Let's say the board needs &gt;2.8V, then we can model our 8.7uA draw as a ~300kΩ load. On the battery curve given above, this corresponds to a lifetime of ~1000 hours, or about 5 weeks. Adding some margin for safety, we may indeed arrive at a reasonable lifetime of 15 days for this device.</p>
<p><img alt="" class="addpic" src="/static/cgmteardown1/11.png"/></p>
<p>Oh also, here's what the power looks like during the Bluetooth transmission. It looks like &gt;50 packets, and averages 100uA for 7 seconds.</p>
<h2 id="question-2-what-other-chips-are-in-this-thing">Question 2: What other chips are in this thing?</h2>
<p><img alt="" class="addpic" src="/static/cgmteardown1/10.jpeg"/></p>
<p>The nRF is easily identified, but I cannot figure out what the other two are. The top has a small IC which seems to be connected to the larger antenna (possibly RFID?), and when I look up U78 it turns up an UHF amplifier (possible) with a different package, the 3SK206. But UHF antennas are pretty big usually, at least bigger than the Bluetooth one. </p>
<p>The other big chip might be an analog frontend for the glucose needle electrode (the traces for that go right towards it). If so, it's amplifying the readout voltage from the glucose sensor before being digitized by the nRF, but again I could not figure out the specific part. Closest part marking I could find was a step-up converter, but that doesn't seem likely.</p>
<h2 id="question-3-whats-the-bom">Question 3: What's the BOM?</h2>
<p>I'm pretty interested in this, just because I like knowing internal info about consumer products. But until I figure out the other chips, I can't really give a good estimate for cost. I also have no idea what the encasement or needle or assembly cost, and the app is pretty nice too and that can't be easily included in cost of the product. The stuff I do know: the nRF costs about 2-3$ in bulk, passives I can't imagine exceed 50 cents in total, and the board is likely under 1$. I'm not too bummed by cost however, since in France the Libre Freestyle 2 is 40 Euro and I can't imagine them making much money over there. </p>
<h1 id="conclusion">Conclusion</h1>
<p>Knowing that the total power consumption is 8.7uA at 3V (~30uW) I think we can power this device using energy harvesting. For reference, I've seen a 4cm^2 solar panel receive nearly 3 mW in direct sunlight. Imagine a permanent CGM, powered simply by going outside in the sun for 15 minutes a day. What a life!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/cgmteardown1/</guid>
      <pubDate>Sun, 06 Oct 2024 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Solar energy harvesting</title>
      <link>https://andykong.org/blog/eetestr1801k/</link>
      <description>Testing out the R1801K</description>
      <content:encoded><![CDATA[<html><body><p>Hello!</p>
<p>I want to build devices which can charge themselves using energy sources present in the environment. Currently I'm trying out solar.</p>
<h1 id="background-on-solar">Background on solar</h1>
<p>Even though a solar cell generates its max voltage in the presence of almost any light, they are high-resistance sources i.e. you can't just connect them directly into your battery. For maximum power, you need to pull just enough current to reduce the solar cell's output to ~80% of its peak voltage, as shown in the chart below. Chips that intelligently track this peak call it MPPT (maximum power point tracking). </p>
<p class="caption"> [source](https://www.atonometrics.com/applications/what-is-a-pv-module-iv-curve/) </p>
<p><img alt="source" class="addpic" src="/static/eetestr1801k/0.png"/></p>
<p>For my device, the TI BQ25570 is nearly perfect — it offers MPPT and starts from only 600 mV. However, it requires a certain amount of power to start up, around 15 uW. Since I'm expecting almost no light on my device, I wanted to find a chip which starts from even less power. This is where the R1801K comes in — this is a chip made by Nisshinbo that claims to begin harvesting at only 1 uW!</p>
<p><img alt="" class="addpic" src="/static/eetestr1801k/1.png"/></p>
<p>The only catch is that it requires 4 V, but I think the voltage will be easier to get than the power. So I bought the chip, and my friend Injoo made a little breakout board for me which pulls charge from solar cell into a storage capacitor. </p>
<p><img alt="" class="addpic" src="/static/eetestr1801k/2.jpg"/></p>
<p>To simulate a constrained power source, I'm passing 4 V across a 1 MΩ resistor, resulting in an output power of 16 uW. Then I connected my board.</p>
<p><img alt="" class="addpic" src="/static/eetestr1801k/3.jpeg"/></p>
<p>Initially, the storage capacitor filled up steadily, but at some point my capacitor voltage was just holding still. To compensate, I increased my power supply voltage and the charging started again. Again, after a bit the charging would stop. </p>
<p><img alt="" class="addpic" src="/static/eetestr1801k/4.jpeg"/></p>
<p>I was powering the device on 4.8V, and the output voltage could go no further than 1.5V. I realized at this point that my capacitor was probably leaking, and checked out the datasheet. Lo and behold, a leakage current of 12.6 uA! Assuming this happens at the rated voltage (6.3 V), then the capacitor's resistance is 500kΩ. This meant the R1801K was desperately pumping in charge which was being leaked out at the same rate by the capacitor, and at 1.5V, that rate was 3uA. </p>
<p><img alt="" class="addpic" src="/static/eetestr1801k/5.png"/></p>
<p>Because this is pretty close to the max current that the R1801K has access to (4.8V/1MΩ = 4.8uA), I'm pretty happy with the performance of this chip even though it couldn't overcome the leakiness of my capacitor. I now also have to think about alternative energy storage methods like SMD solid-state batteries, which are a lot less straightforward to buy. But hey, limitation breeds creativity. </p>
<h1 id="big-picture">Big picture</h1>
<p>I'm currently working on an energy harvesting device for my Hackaday talk coming up in November, and in the process I need to evaluate a bunch of the elements of an energy harvesting device (solar/RF/thermal, harvesting chips, storage methods, low-powered microcontrollers, communication methods). I don't think it'll be worth it to try out everything from every category, but I still love testing out chips just to make sure they do what they say on the tin. Anyway, cya later!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/eetestr1801k/</guid>
      <pubDate>Sun, 25 Aug 2024 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Edge Esmerelda Experience</title>
      <link>https://andykong.org/blog/eee/</link>
      <description>Popup City No. 2</description>
      <content:encoded><![CDATA[<html><body><p>Hello! Again I have returned from a distant land with tales from a popup community. This city was/is called <a href="https://www.edgeesmeralda.com/">Edge Esmerelda</a>, and is/was located in Healdsburg, CA.  </p>
<p><img alt="" class="addpic" src="/static/eee/0.png"/></p>
<p>In part, going was an experiment. I remember reading someone's blog post about "asking favors/questions you are SURE will be refused" as a way to make sure you really know the bounds of your social position (a la <a href="https://www.youtube.com/watch?v=vKA4w2O61Xo">Veritasium</a>) — I did this about a year ago when I asked 3 people if I could borrow 20,000 Swiss Francs so I could stay in Switzerland. None of them had it, but all of them said yes.</p>
<p>Regarding Edge Esmerelda, I had just recently gone to SF and did not really want to go back so soon, but decided that if it was free then I would. I DM'd one of the organizers and asked if I could come for a week if I sorted my own housing, and they were very generous to me. Every time I do this exercise I am surprised.</p>
<p>Anyway. Here's what</p>
<h1 id="edge-esmerelda-is-like">Edge Esmerelda is like</h1>
<h2 id="college">College</h2>
<p>College is a sweet time where all your friends live together and hang out often, and everyone you meet has a lot of potential. I am not <em>that</em> old, but so far life has not been like that. But Edge was, and it made me miss living near cool people. </p>
<h2 id="harvard">Harvard</h2>
<p>Through one lens, Harvard is a place where smart people and rich people (not mutually exclusive) go to mingle. At the end, everyone gets a Harvard degree, and it's hard to tell which is which. In my first few days at Edge, I met many incredible biologists and engineers working on crazy ideas — measuring brain activity using lasers, airships for cargo and surveying, and balloons which slow down global warming. Later on, I met some crypto people who also founded hard tech companies. </p>
<h2 id="a-conference">A Conference</h2>
<p>Most conferences I've been to go something like this: during the day people give talks and listen, groups go off to dinner, and then every night there's a party. If you tag along to dinner with someone interesting, you do get to chat with them, but otherwise you can't because it's either between sessions (rushed) or after drinks (incapacitated).</p>
<p>Being at Edge for a week felt like the perfect conference — there was no rush, but you still felt urgency to talk to interesting people. And there were events, but an interesting conversation took precedence. And because the talks were noncommittal, they were not one-sided since everyone who attended had a stake in the discussion that followed. I quickly found a stable group, but there were new faces to meet at every meal. If Edge was a conference it would be a perfect one.</p>
<h2 id="america">America</h2>
<p>Massive simplfiication but stick with me: America was founded when a group of people unsatisfied by their local community chose to go somewhere else with all their homies. In 2024, new land is in short supply so "somewhere else" becomes "anywhere &gt;2 hours away from a major airport". Also in 2024, Protestantism has been superseded by scientism, and so "religious homies" becomes "science/engineering homies". </p>
<h2 id="a-commune">A Commune</h2>
<p>In exchange for coming, all I had to do was be involved: I spoke with new people every day, gave talks, and attended events. But I really don't think I pulled my weight. Other people organized Pilates sessions, planned hackathons and brainstorming sessions, and threw parties. It felt a lot like my ideal commune — one community, in constant contact, giving and receiving favors so rapidly that a balance is achieved and life is enhanced for everyone. </p>
<h1 id="list-of-stuff-that-happened">List of stuff that happened</h1>
<ul>
<li>
<p>Saw horses (2)</p>
</li>
<li>
<p>I gave a talk on implants</p>
</li>
</ul>
<p><img alt="" class="addpic" src="/static/eee/1.png"/></p>
<ul>
<li>
<p>Showed off my holograms to a ton of people</p>
</li>
<li>
<p>Experienced my first Pilates session</p>
</li>
</ul>
<p><img alt="" class="addpic" src="/static/eee/2.jpeg"/></p>
<ul>
<li>learned you can 3d print spring-y stuff, like this knife!</li>
</ul>
<p><img alt="" class="addpic" src="/static/eee/4.jpeg"/></p>
<ul>
<li>Stayed at Hotel Anson for 3 nights where I made friends with Canadians and non-Canadians alike</li>
</ul>
<p><img alt="" class="addpic" src="/static/eee/3.jpeg"/></p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/eee/</guid>
      <pubDate>Tue, 25 Jun 2024 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>MonoChrome: In Defense Of Using One Chrome Window At A Time</title>
      <link>https://andykong.org/blog/monochrome/</link>
      <description>Browser meta-war</description>
      <content:encoded><![CDATA[<html><body><p>Hello. Today I'm going to tell you about a work-style switch I made about a year ago. First, a tangent.</p>
<h1 id="tangent">Tangent</h1>
<p><img alt="" class="addpic" src="/static/monochrome/0.jpeg"/></p>
<p>In 2021, I watched a coworker swipe up their Mission Control page to reveal ~20 different applications all on the same desktop. Extremely surprised, I asked why they did that, and they said it was faster. At the time, thought of only using 1 desktop was alien to me because I segregated each work subject into a different desktop (PCBs, software, messenger apps).</p>
<p>Later I realized multiple desktops actually do suck — there's a 0.5-1 second delay when switching desktops which cannot be reduced, and my ProMotion 120Hz screen never turns on when I have multiple desktops. This is, I think, a hard limitation meant to guarantee the OS has enough time to shift memory around and load up the main apps in the new window. But it slowed me down, and then I started using a single desktop. </p>
<h1 id="main-point">Main point</h1>
<p>I used to do the same thing with my Chrome tabs. 100 tabs spread across 4 windows, constantly alt-tabbing through to find the right one. One for electronics, one for articles and blog posts, one for wikipedia pages to read, etc. This was a bad strat, but at the time I felt "organized". </p>
<p>But the same thing was true for multiple Desktops, why not apply the same lessons? Now I just keep one window open. If it gets cluttered, I go through and clear it out. Article? Read and close it. Personal blog you want to correspond with? Write them an email and close it. If you don't have time now, just save it in a Google doc or the Notes app, I'm sure you'll get back to it, if it's so important <em>wink wink</em></p>
<p><img alt="" class="addpic" src="/static/monochrome/1.png"/></p>
<p>As an example, my current window (above) is just stuff I need to take notes on. These are all the tabs I have open. Contrast with my old "Session Buddy" saved tab lists which I've never ever looked twice at:</p>
<p><img alt="" class="addpic" src="/static/monochrome/2.png"/></p>
<p><img alt="" class="addpic" src="/static/monochrome/3.png"/></p>
<p>Imagine, I have hundreds of these lists, totally rotting in there. </p>
<p>Seize the day, close those tabs, and take back the RAM for the important stuff: your buttery-smooth 120Hz screen refresh rate </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/monochrome/</guid>
      <pubDate>Wed, 12 Jun 2024 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Practical Quantified Self Data Analysis, Pt. 1</title>
      <link>https://andykong.org/blog/pqsda1/</link>
      <description>Functions you'll need</description>
      <content:encoded><![CDATA[<html><body><p>Hello! Today I'll walk you through my data analysis pipeline for my personal time series data. </p>
<p>The problem with personal data is that it's more of a hobby than a rigorous industrial process — as such, the sensors give patchy, inconsistent data which is hard to align across time. Another issue is the movement of humans vs. the immobility of some sensors means individualized corrections must be made for each type of data depending on the source (air quality reported from your bedroom must be filtered by location, however your watch does not).</p>
<p>This is going to be a ~3 part series on what functions, broadly, are needed for a complete data analysis suite. I'm going to share my philosophy on this analysis and some code gists, but everything should be simple enough to adapt to other formats/libraries/languages. Let's begin!</p>
<h1 id="analysis">Analysis</h1>
<p>In my system, there are only two kinds of data: continuous data (heart rate, CO2, location) and event data (daily HRV, sleep session, coffee intake). They differ mainly by the frequency that the data is sampled. For me, here are the main kinds of graphs that I'm interested in producing:</p>
<ol>
<li>
<p>Continuous vs. continuous (e.g. blood glucose vs. heart rate, scatterplot)</p>
</li>
<li>
<p>Continous after event (heart rate after drinking coffee, time series)</p>
</li>
<li>
<p>Continuous vs. event (average tetris score vs. previous sleep duration, scatterplot) </p>
</li>
<li>
<p>Event vs. event (daily HRV vs. sleep duration, scatterplot)</p>
</li>
<li>
<p>Event average after event (sleep quality after working out, bar chart)</p>
</li>
</ol>
<p>These are not many, and they cover much of what is needed. Interestingly enough, these can all be constructed through the use of two functions. </p>
<h1 id="function-1-interpolation-by-timestamp-analyses-1-4">Function 1: Interpolation by timestamp (Analyses 1, 4)</h1>
<p>Most continuous sensors will not share timestamps, so you will need to align them (tossing stuff that doesn't line up perfectly) or "align them" (interpolate the denser value with a sparser one to get matching rows). Since we don't usually have a ton of data to spare, we go for interpolation. The code is simple, and just uses <code>np.interpolate</code></p>
<p><img alt="" class="addpic" src="/static/pqsda1/0.png"/></p>
<p>This is preferred for continuous vs. continous, but for event vs. event much can happen on longer timescales so I think interpolation is quite lossy. </p>
<h1 id="function-2-select-around-timestamp-analyses-2-3-5">Function 2: Select around timestamp (Analyses 2, 3, 5)</h1>
<p>Since these analyses all use event data, we will select y-variable data that lines up with the event timestamp. This function just slices out time ranges from the y-variable that are specific offsets from each x-variable event. </p>
<p><img alt="" class="addpic" src="/static/pqsda1/1.png"/></p>
<p>Once each time range is found, we toss the ranges with zero elements and then have a list of dataframes. Just for fun, I added an "eventOffset" column for each sample so we can <code>hstack</code> them into a single DataFrame if desired. Here's one element from the list of my heart rate dataframes. </p>
<p><img alt="" class="addpic" src="/static/pqsda1/2.png"/></p>
<p>The <code>a</code> variable was created by running <code>a = s1.selectAround(doseTimes, beforeHours=2, afterHours=4)</code>, selecting time ranges that included each doseTime and data from 2 hours before and 4 hours after. Once the list is made, you can just plot each time range on the same graph:</p>
<p><img alt="" class="addpic" src="/static/pqsda1/3.png"/></p>
<p>Or more cleanly, plot a smoothed mean of the same data:</p>
<p><img alt="" class="addpic" src="/static/pqsda1/4.png"/></p>
<p>Another fantastic use case of <code>selectAround</code> is that I can select data that happened exclusively after or before an event time. For instance, if I wanted to see all sleep sessions happening within 6 hours of a caffeine event, I could run <code>sleep.selectAround(doseTimes, 0, 6, cutoff=0, surroundRequired=False)</code> and get the list of every time that happened. The inclusion of the <code>timeOffset</code> col also means I can scatterplot the sleep data once selected:</p>
<p><img alt="" class="addpic" src="/static/pqsda1/5.png"/></p>
<p><code>selectAround</code> also offers negative lookback and lookahead, so you can do whatever you need. </p>
<h1 id="conclusion">Conclusion</h1>
<p>Anyway, I think these two primitives are all you need to find any personal insights you might wish. You may also want to use filters (air quality data bool'd by location, or sleep session filtered by coffee consumption), but that will be saved for the next post.</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/pqsda1/</guid>
      <pubDate>Tue, 07 May 2024 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Different ways to black-out holograms</title>
      <link>https://andykong.org/blog/hologramblackout/</link>
      <description>Paint vs. tape</description>
      <content:encoded><![CDATA[<html><body><p>Hello! Recently I made my first succesful batch of holograms. Here they are:</p>
<blockquote class="twitter-tweet tw-align-center" data-media-max-width="560"><p dir="ltr" lang="en">Made some holograms yesterday, the best I’ve gotten far! Shoutout to Ultimate Holography <a href="https://t.co/eatJqhVmJY">pic.twitter.com/eatJqhVmJY</a></p>— Andy (@oldestasian) <a href="https://twitter.com/oldestasian/status/1784001625549332548?ref_src=twsrc%5Etfw">April 26, 2024</a></blockquote>
<script async="" charset="utf-8" src="https://platform.twitter.com/widgets.js"></script>
<p>These holograms use the same silver halide particles as analog film photography, but with approximately 100x smaller crystal size (~10nm). Crystals of silver halide are suspended in gelatin and coated onto one side of the glass plate, this plane is then photoactive.</p>
<p>After a hologram is shot, it's good practice to black out the gelatin side of the hologram as it both protects them and makes them easier to see. After shooting my holograms, I wanted to compare a couple ways to do it and document my results.</p>
<h1 id="black-tape">Black Tape</h1>
<p>The funny thing about black tape (duct/gaffer) is that usually the sticky side is white. I found electrical tape to have a black sticky side, but wasn't able to find any that was wide enough to cover my glass plates in one piece. I tried abutting them edge-to-edge instead. </p>
<p class="caption"> Tape abutment works ok for blacking out holograms </p>
<p><img alt="Tape abutment works ok for blacking out holograms" class="addpic" src="/static/hologramblackout/0.jpg"/></p>
<p>You'll notice this kinda works, but the gaps are clearly visible. Even for perfect alignment, there will be a noticeable thin line. There's also some bubbles — these are easy enough to get out by scratching at the tape so it flattens out, but sometimes there are bigger bubbles which do not come out easily. If done carefully it won't look too bad. </p>
<p class="caption"> Air bubbles trapped in the tape backing. Remember manually putting on phone screens like this? </p>
<p><img alt="Air bubbles trapped in the tape backing. Remember manually putting on phone screens like this?" class="addpic" src="/static/hologramblackout/1.jpg"/></p>
<h1 id="black-tape-overlap">Black Tape, Overlap</h1>
<p>If the tape is instead placed over the next piece, this line only gets worse. I cannot recommend this method cause it really sucks. </p>
<p class="caption"> Overlapping tape backing </p>
<p><img alt="Overlapping tape backing" class="addpic" src="/static/hologramblackout/2.jpg"/></p>
<p><img alt="" class="addpic" src="/static/hologramblackout/3.jpg"/></p>
<h1 id="paint">Paint</h1>
<p>Lastly, I tried a matte black spray paint. One nice thing about this is that we don't really care about the finish on the back side so you can spray a pretty thick single coat. Here is a sheet I taped and spray-painted for comparison. </p>
<p class="caption"> Holograph plate taped (left) and spray painted (right) </p>
<p><img alt="Holograph plate taped (left) and spray painted (right)" class="addpic" src="/static/hologramblackout/4.jpg"/></p>
<p class="caption"> Difference in black effectiveness from the visible side </p>
<p><img alt="Difference in black effectiveness from the visible side" class="addpic" src="/static/hologramblackout/5.jpg"/></p>
<p>In the circle above you can see the difference in contrast between tape (bottom) and paint (top). I would say the spray paint is a tad darker, and can go on more evenly. This is definitely the nicest method. </p>
<p>Anyway, now you know!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/hologramblackout/</guid>
      <pubDate>Sun, 28 Apr 2024 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>How To Set ATtiny Fuses (with Arduino as ISP)</title>
      <link>https://andykong.org/blog/attinyfuses/</link>
      <description>Change clocks, reduce startup delay, and more!</description>
      <content:encoded><![CDATA[<html><body><p>Hello! This post will show you how to set the fuses on an ATtiny using the Arduino IDE and an Arduino as ISP. You'll need to do this to configure some new options on the ATtiny that aren't available in the Arduino IDE dropdown. We're gonna start off where we left off in the <a href="../ard2attiny">previous blog post</a>, so be sure to follow that through if you haven't yet.</p>
<p class="caption"> The incomplete fuse options offered by the Arduino IDE </p>
<p><img alt="The incomplete fuse options offered by the Arduino IDE" class="addpic" src="/static/attinyfuses/0.png"/></p>
<h1 id="motivation">Motivation</h1>
<p>Recently, I wanted to use energy harvesting to power an ATtiny. Every electron is precious in such a scenario, so I had none to waste. However, on startup, I noticed my ATtiny was actually just doing nothing for around 60ms after power-on!</p>
<p class="caption"> 60ms delay after startup on the ATtiny. Blue is power rail, yellow is the signal out </p>
<p><img alt="60ms delay after startup on the ATtiny. Blue is power rail, yellow is the signal out" class="addpic" src="/static/attinyfuses/1.png"/></p>
<p>Digging into the datasheet, I found that by default the ATtiny waits 64ms after startup to give the clock time to stabilize. While this is all nice and good, I don't have that kind of power to waste! This could be changed to 4ms or 0ms by setting the LFUSE (datasheet page 26), but I had no idea how — all I knew were the options on the Arduino IDE. From more reading, I learned that the fuses were what changed every time I altered the clock frequency or other pre-code options then hit Burn Bootloader, but I still didn't know how to upload non-common options.</p>
<p><img alt="" class="addpic" src="/static/attinyfuses/2.png"/></p>
<h1 id="custom-fuses">Custom fuses</h1>
<h2 id="burn-bootloader-command-from-the-cli">Burn Bootloader command from the CLI</h2>
<p>On a forum post someone mentioned you could burn the fuses from the command line using the same command that the Arduino IDE used to burn the bootloader (fuses included). To get the starter command, you can go to Settings-&gt;verbose output and turn it on for upload. Next time you hit the Tools-&gt;Burn Bootloader button, at the very top of the stdout box will be the command that the IDE tried to run.</p>
<p class="caption"> String to steal from the Arduino IDE stdout </p>
<p><img alt="String to steal from the Arduino IDE stdout" class="addpic" src="/static/attinyfuses/3.png"/></p>
<p>When we copy this line elsewhere, we can see exactly where the fuses are in the command. They're towards the end, and encoded as bytes. If you ran this in the terminal, it would do the same thing that hitting "Burn Bootloader" in the IDE does. </p>
<p class="caption"> Pretty-printed Burn Bootloader command from the previous image </p>
<p><img alt="Pretty-printed Burn Bootloader command from the previous image" class="addpic" src="/static/attinyfuses/4.png"/></p>
<h2 id="determining-fuse-byte-values">Determining fuse byte values</h2>
<p>One way to set the fuses is to figure out exactly which bits of the LFUSE bytes alter the startup time. In my case, SUT should be 00 for 0ms delay, meaning the right byte should be changed in LFUSE (0x62 above). This approach is possible albeit annoying, and I worried that I had the wrong endian-ness all the time.</p>
<p><img alt="" class="addpic" src="/static/attinyfuses/5.png"/></p>
<p>The second time, I realized there were several fuse calculators webpages (like <a href="https://eleccelerator.com/fusecalc/fusecalc.php?chip=attiny85">this</a> or <a href="https://www.engbedded.com/fusecalc/">this</a>) where you could pick the options and it would auto-generate the correct fuse value. Super!</p>
<p><img alt="" class="addpic" src="/static/attinyfuses/6.png"/></p>
<p>Once you figure out the fuse bytes, just edit the original Arduino IDE string to include the new values for the appropriate fuses. You can then just run the command in your computer's terminal to set the new fuses on your ATtiny. </p>
<p>In my case, I just changed the 0x62 in -Ulfuse to 0x42. Now my startup time is &lt;1ms!</p>
<p class="caption"> No more startup delay </p>
<p><img alt="No more startup delay" class="addpic" src="/static/attinyfuses/7.png"/></p>
<p>Alright, that's all. Cya around!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/attinyfuses/</guid>
      <pubDate>Tue, 23 Apr 2024 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Immersive Space Workarounds In The Vision Pro</title>
      <link>https://andykong.org/blog/quickimmersion/</link>
      <description>Quick Immersion</description>
      <content:encoded><![CDATA[<html><body><p>Hello!</p>
<p>Apple's VisionOS is the first AR headset I've worn for more than a few hours, and I've been writing VisionOS apps for about a week now.</p>
<h1 id="no-gestures">No Gestures?</h1>
<p>So far, the thing that bothers me the most about VisionOS is the inability to use custom gestures in the main "area". The Vision Pro is mostly billed as a productivity tool, and while working in it I wanted to do hand gestures to open specific webpages or send data to some program for processing. Unfortunately, accessing hand tracking requires ARKit, which requires the app to be in an "Full Space", which closes all other apps (e.g. Safari, Macbook screen mirroring). This is intentional on Apple's part, possibly to prevent custom gestures from interfering with the system-level "Tap" and "Drag" motions.</p>
<p class="caption"> Apple's justification </p>
<p><img alt="Apple's justification" class="addpic" src="/static/quickimmersion/apple.png"/></p>
<p>I'm not sure what privacy would be protected by restricting ARKit usage like this, but I think custom gestures would be really cool to enable spatial computing and I want to do it. What are our alternatives?</p>
<h1 id="quick-immersion">Quick Immersion</h1>
<p>I've been experimenting with opening a Full Space and closing it really quickly, essentially using an app's windowed version as a toggle for the app's Full Space. The Full Space and the user's workflow can then complement each other — I do some 3D modelling on my laptop's shared screen, launch my app's Full Space for life-size model viewing and tweaks, then return to my desktop with a quick gesture. Here's some demo photos.</p>
<p class="caption"> 1. User is using another app (reading about potatoes) </p>
<p><img alt="1. User is using another app (reading about potatoes)" class="addpic" src="/static/quickimmersion/1.jpg"/></p>
<p class="caption"> 2. User decides to go into an Immersive Space to work with a 3D asset </p>
<p><img alt="2. User decides to go into an Immersive Space to work with a 3D asset" class="addpic" src="/static/quickimmersion/2.jpg"/></p>
<p class="caption"> 3. User spawns a cube in the Full Space. Note the Safari window has closed on the left </p>
<p><img alt="3. User spawns a cube in the Full Space. Note the Safari window has closed on the left" class="addpic" src="/static/quickimmersion/3.jpg"/></p>
<p class="caption"> 4. Satisfied with their cube, the user performs the exit gesture </p>
<p><img alt="4. Satisfied with their cube, the user performs the exit gesture" class="addpic" src="/static/quickimmersion/4.jpg"/></p>
<p class="caption"> 5. The previous apps return when the Full Space is closed and the user can resume their reading </p>
<p><img alt="5. The previous apps return when the Full Space is closed and the user can resume their reading" class="addpic" src="/static/quickimmersion/5.jpg"/></p>
<p>In the past I've seen apps with an exit button to get out of the Full Space. by using a custom gesture, exiting out takes only about a second — the approximate delay when switching between desktops on the Macbook. It's been quite comfortable to use but I haven't found a really useful thing to do with it. Here's some ideas I had though:</p>
<hr/>
<h2 id="inventory-app">Inventory app</h2>
<p>An "inventory" website can have drag-n-drop slots for objects (3D meshes, URLs, text, files) and share those objects with an Immersive app. A user can drag assets from their computer into the inventory site, enter the Full Space to work with the assets as objects, return them to the inventory, and then exit the Full Space and get the finished files off the website.</p>
<p>Since meshes/objects in a Full Space can communicate on-interaction (such as sending a GET request), the website can know what's happened to its own objects and persist those changes outside the space.</p>
<h2 id="3d-viewer">3D viewer</h2>
<p>A classic AR use case is viewing life-scale 3D models. An engineer in the middle of PCB design can visualize several boards and connectors in the Full Space, ensuring they fit together. A designer can export their couch design halfway through the process to see how their chosen materials look in real environments, or to check if the approximate sizes make sense. </p>
<hr/>
<h1 id="conclusion">Conclusion</h1>
<p>Anyway, just wanted to let y'all know that this was possible. The code is in a <a href="https://github.com/kongmunist/QuickImmersion">Github repo</a> and it does a few other things (spawning cubes that bounce around the room), but the Full Space opening/closing is simple enough that I hope you can find it. Cya! </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/quickimmersion/</guid>
      <pubDate>Sun, 21 Apr 2024 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>A Layman's Guide To The ICNIRP Guidelines On Limiting Exposure To Electromagnetic Fields (100kHz-300GHz)</title>
      <link>https://andykong.org/blog/icnirp/</link>
      <description>I read this and it was actually interesting</description>
      <content:encoded><![CDATA[<html><body><p>Hello!</p>
<p>In this post I'm going to sum up all the cool things I learned about the human body and the way the body interacts with electromagnetic (EM) radiation. All these facts I learned from a casual perusal of the ICNIRP 2020 guidelines doc, which you can find yourself <a href="https://www.icnirp.org/cms/upload/publications/ICNIRPrfgdl2020.pdf">here</a>.</p>
<p class="caption"> ICNIRP guidelines were probably not read by the creator of this meme </p>
<p><img alt="ICNIRP guidelines were probably not read by the creator of this meme" class="addpic" src="/static/icnirp/babies.png"/></p>
<p>ICNIRP is an international non-profit made up of random scientists who enjoy quantifying electromagnetic radiation safety. They read the most up-to-date literature every few years and compile a large guidelines doc describing limits of what the human body. These guidelines are then used by researchers and government regulatory bodies to establish safe legal bounds on human-EM exposure.</p>
<p><img alt="" class="addpic" src="/static/icnirp/0.png"/></p>
<h1 id="why-did-you-read-this-dry-and-boring-safety-text">Why did you read this dry and boring safety text?</h1>
<p>Recently, I needed to describe the safety of an electromagnetic system that interacted with the human body. To do so, I needed to show that the design conformed to the ICNIRP guidelines — this was a task I had been putting off in case A) the system wasn't actually safe, or B) the guidelines would be complex and hard to understand. </p>
<p>I was close to giving up in favor of relying on a secondary source's interpretation of the ICNIRP guidelines, but in a moment of clarity, I decided to first give the primary text my best crack. And thank goodness! The text is full of interesting little details, explanations of why the limits are what they are, and the different ways EM radiation can damage the human body. I found the info I needed and got out, but I found the guidelines so interesting that I told myself I'd revisit them after my deadline. </p>
<p>The rest of this blog will be in list format, interrupted by occasional screenshots. </p>
<hr/>
<h1 id="interesting-electromagnetic-trivia">Interesting electromagnetic trivia</h1>
<ul>
<li>
<p>ICNIRP only acknowledges three ways EM radiation can harm human tissues: 1) electric fields below 10MHz can stimulate nerves, 2) biological membranes can break down or change permeability in response to sufficiently strong fields, and 3) EM radiation can cause tissue heating.</p>
</li>
<li>
<p>At higher frequencies &gt;10MHz, damage from tissue heating (3) happens way before cell permeability changes (2), so we can ignore 2 as long as we respect the guidelines for (3). At lower frequencies &lt;10MHz, nerve activation (1) happens way before the permeability stuff (2), so again we can ignore 2 as long as we follow the guidelines for (1). At 10MHz, changes in permeability kill you immediately (just joking)</p>
</li>
</ul>
<p><img alt="" class="addpic" src="/static/icnirp/1.png"/></p>
<ul>
<li>
<p>The frequencies that can affect nerves are limited to ~100kHz and are described by participants as tingling. As frequency increases to 10MHz, this sensation becomes one of "warmth" and we start to care more about thermal safety. Nerve effects are mainly described in the other ICNIRP doc for lower frequencies.</p>
</li>
<li>
<p>Waves at a higher frequency penetrate less deeply into human tissues. Because of this, below 6GHz the guidelines are concerned with measuring temperature rise with "specific energy absorption rate" (SAR, W/kg), and above 6GHz they measure temperature rise with "absorbed power density" ($S_ab$, W/$m^2$). </p>
</li>
<li>
<p>Humans' core body temperature is 37°C and can vary up to 1°C throughout a day. Correspondingly, the guidelines try to keep EM radiation exposure below a power level that increases body temperature 1°C (as a conservative limit, they actually try to keep it under 0.1°C). At the 100kHz-6GHz range, a SAR of approximately 6 W/kg leads to a core body temperature increase of 1°C. The issue with increased body temperature is that the heart has to work harder and it causes more accidents</p>
</li>
</ul>
<p><img alt="" class="addpic" src="/static/icnirp/2.png"/></p>
<p><img alt="" class="addpic" src="/static/icnirp/3.png"/></p>
<ul>
<li>From this we also learn that children can dissipate heat better than adults and therefore have a higher SAR threshold.</li>
</ul>
<p><img alt="" class="addpic" src="/static/icnirp/4.png"/></p>
<p><img alt="" class="addpic" src="/static/icnirp/5.png"/></p>
<ul>
<li>ICNIRP also gives us a neat reference for how much power an adult human uses normally. At rest 1W/kg, at stand 2W/kg, and 12W/kg running. I guess this means standing desks actually work?</li>
</ul>
<p><img alt="" class="addpic" src="/static/icnirp/6.png"/></p>
<ul>
<li>Earlier I mentioned the distinction between surface and deep tissue EM energy absorption. The doc elaborates that at 6GHz, 86% of the power is absorbed in the first 8mm of skin. Surface heating is also less worrisome because we can get rid of it more easily.</li>
</ul>
<p><img alt="" class="addpic" src="/static/icnirp/7.png"/></p>
<ul>
<li>More tangential trivia, almost all human tissues get damaged beyond 42°C, exhibiting very little interperson variation. To respect this limit, ICNIRP tries to limit localized tissue heating to 41°C. </li>
</ul>
<p><img alt="" class="addpic" src="/static/icnirp/8.png"/></p>
<ul>
<li>Extremeties are usually around 33-36°C, while core tissues are closer to 38°. Therefore extremeties are limited in heating to +5°C, while core is limited to +2°C. </li>
</ul>
<p><img alt="" class="addpic" src="/static/icnirp/9.png"/></p>
<ul>
<li>Testicles stop making sperm when people sit down because they heat up. I wonder if the increase in desk jobs is responsible for the global decline in sperm count? </li>
</ul>
<p><img alt="" class="addpic" src="/static/icnirp/10.png"/></p>
<ul>
<li>They approximate whole-body SAR by using a 10g meat cube. They say the spread of heat in a 10g mass is "close enough" to a larger mass. </li>
</ul>
<p><img alt="" class="addpic" src="/static/icnirp/11.png"/></p>
<ul>
<li>ICNIRP wants to limit body temperature rises to 0.1C, because of this they choose a whole-body SAR limit of 0.4W/kg. For civilians who don't know what they're up against, the limit is 5x better at 0.08W/kg. This reduction is a bit arbitrary to me, but I appreciate the safety factor. </li>
</ul>
<p><img alt="" class="addpic" src="/static/icnirp/12.png"/></p>
<ul>
<li>Old people sweat 25% less effectively than younger people</li>
</ul>
<p><img alt="" class="addpic" src="/static/icnirp/13.png"/></p>
<hr/>
<p>I realized that 5G cell phones use frequencies covered by these guidelines (450MHz-6GHz and 24-52GHz). In light of ICNIRP's explanations, people who freak out about 5G affecting their babies don't make much sense — it's just the effects of heating, and our communications are so efficient that the heating is not even significant. While I enjoy doing my own research, these people should get better at it before being so vocal. </p>
<p><img alt="" class="addpic" src="/static/icnirp/babies.png"/></p>
<p>It's cool: reading these guidelines I get a better understanding on how to think about quantifying safety. I also realize how much harder it would have been to spec safety without everything compiled together — thank you ICNIRP!</p>
<p>Anyway, I hope you successfully stay away from dangerous EM waves until I see you next. Cya next time!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/icnirp/</guid>
      <pubDate>Tue, 16 Apr 2024 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Most-scanned bulletin boards at CMU</title>
      <link>https://andykong.org/blog/cmuads/</link>
      <description>Finally publicizing some sneaky data collection from 4 years ago</description>
      <content:encoded><![CDATA[<html><body><p>Hello!</p>
<p>Back when I attended CMU, there were these massive bulletin boards around campus full of adverts from students and profs. Students usually promoted their app/club/org/student course, and professors pushed their classes.</p>
<p><img alt="" class="addpic" src="/static/cmuads/0.jpg"/></p>
<p>When I started putting my own ads on these boards, I was always curious which b-boards were the most frequently looked at. For instance, the board between the Sorrells library and the bathroom gets a lot of traffic, but the people walking there were probably pretty focused on studying. Whereas the UC bulletin boards get less foot traffic, but the people there seem calmer since they aren't walking fast to get to class.</p>
<p>Anyway, when I finished a campus events aggregator called <a href="../../projects/carnegiecalendar">Carnegie Calendar</a> and wanted to promote it by putting up bulletins, I finally had my chance to determine the highest engagement b-board.</p>
<p>I printed out 63 of these flyers promoting Carnegie Calendar, each with a unique QR code demarcated by this little number in the corner. Each QR code led to the same website with a different trailing URL argument, which my server recorded in a text file. </p>
<p class="caption"> See the little 1? </p>
<p><img alt="See the little 1?" class="addpic" src="/static/CCzoomin.png"/></p>
<p>I got a bunch of friends to help me put up the QR codes (Thanks Nancy Sam Ruijie and I think Nate?) and waited for the data to roll in.</p>
<p>I always wanted to sell this data to some overzealous student group that puts up a ton of flyers and cares about it, but after 4 years I'm realizing that's probably not gonna happen. So I've tabulated the results into this map below.</p>
<p><img alt="" class="addpic" src="/static/cmuads/1.png"/></p>
<p>More granularly, the per-bulletin board data can be found <a href="https://docs.google.com/spreadsheets/d/1T-BplbYhJhCCI-hyfR-BIS-O5AUM7uf--s4L8abqz_0/edit#gid=1010815453">here</a>.</p>
<p>A day or two after all the flyers were up, I realized that it would be free traffic if I also advertised the website in the school Facebook groups. I made a separate URL extension for the group links and posted it. The single Facebook link received around 6x the visits to all in-person links despite only being up for like 8 hours. </p>
<p><img alt="" class="addpic" src="/static/cmuads/2.png"/></p>
<p>As with all deploys, some event I scraped the first day of public release ended up busting the website, and I didn't realize until noon the next day. I always wonder about those missed first impressions. Anyway, enough lore for the day. Cya next time!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/cmuads/</guid>
      <pubDate>Mon, 15 Apr 2024 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Is Morse Code Letter-Frequency Optimal?</title>
      <link>https://andykong.org/blog/omorse/</link>
      <description>Most common letters vs. Morse code length</description>
      <content:encoded><![CDATA[<html><body><p>Hello! Recently I completed Google's <a href="https://morse.withgoogle.com/learn/">Morse Typing Trainer</a> (I highly recommend it) and read a lot about the history of International Morse Code.</p>
<p>The inventor of Morse code mentions counting the occurrences of letters in printer's type in order to dole out the shorter codes to more commonly used letters. For example, E and T are both single-symbol, while something like Z requires 4 symbols.</p>
<p>I was interested in seeing just how good Morse got his letter frequencies, so I got a letter frequency table and the Morse code chart and correlated them against each other.</p>
<h1 id="morse-code-charlength-vs-letter-frequency">Morse Code Char-Length vs. Letter Frequency</h1>
<p><img alt="" class="addpic" src="/static/omorse/0.png"/></p>
<p>Further right is more common, further up is longer Morse sequences. We see some glaring gaps — O should possibly be a 2-char symbol instead of 3, maybe replacing M? But overall this follows the trend of less-common letters getting the longer end of the Morse code. </p>
<hr/>
<h1 id="morse-code-timelength-vs-letter-frequency">Morse Code Time-Length vs. Letter Frequency</h1>
<p>Then I also wanted to know if the time length of each Morse letter also matched the letter frequency — the idea being that more common letters should be shorter in duration to make them easier to type. Morse is defined around the duration of a single dot — inter-symbol gaps are a dot long, and a dash is 3 dots long.</p>
<p><img alt="" class="addpic" src="/static/omorse/1.png"/></p>
<p>Here is the same graph as above, but instead of Morse symbol length I'm plotting time length for each letter. The gaps here are even more extreme. "O" is typed out as "___", taking 11 dots of time despite being the 4th most common letter. "I" (..) appears less often than "A" (._), but is shorter to type. </p>
<p>It's obvious that Morse code is clearly un-optimized for typing speed, which suggests that transmit speed wasn't actually that important in practical use. It is kinda frustrating that they didn't add such a simple improvement even though it would have helped a decent amount (10-20% I'm estimating).</p>
<hr/>
<h1 id="morse-transmit-speed-optimization">Morse Transmit Speed Optimization</h1>
<p>While I know Morse was developed for terrible communication channels and could only transmit one tone, I couldn't help but think about the potential improvements especially in regards with multi-tone. If two tone were possible, a dash could be converted from a long symbol into a short dot in the other frequency.</p>
<p>This graph compares the transmit time-lengths for each letter if dashes were 3 or 1 dots long. Y-axis is frozen for easier comparing. By adding one tone we can decrease time for any letter with a dash.</p>
<p><img alt="" class="addpic" src="/static/omorse/3.png"/></p>
<p>Here's a harder-to-read improvements chart</p>
<p><img alt="" class="addpic" src="/static/omorse/2.png"/></p>
<p>Finally, the approximate speedup offered by switching dashes to dots, and then further even removing the spaces between letters.</p>
<p><img alt="" class="addpic" src="/static/omorse/4.png"/></p>
<p><img alt="" class="addpic" src="/static/omorse/morsespeedup.png"/></p>
<p>So the total improvement isn't nuts, around 50% in optimal cases with dashes being 1 dot long. This doesn't even account for possible improvements when properly staggering the Morse length vs. letter frequency. </p>
<hr/>
<h1 id="who-cares-why-are-you-learning-outdated-radio-speak">Who cares, why are you learning outdated radio speak?</h1>
<p>I'm currently pretty interested in transmitting information through the skin, and I think Morse code would be a neat way to do it. I want to use vibration motors for producing this haptic info, and they can't render speech directly but can usually make a single buzz as required by Morse code. However, the vibromotors usually can also produce a couple of discernible vibrations which can make up our 2-tone Morse code. </p>
<p>I realized after making all these graphs that optimizing transmit times doesn't necessarily make it any easier to receive Morse signals faster. However, my hope is that the brain's short-short-term cache would have more retention to hear the receiver signal if the letters are transmitted faster.</p>
<p>That's all for now, cya!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/omorse/</guid>
      <pubDate>Fri, 12 Apr 2024 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Power profiling a thermal printer camera</title>
      <link>https://andykong.org/blog/dinocamtoypower/</link>
      <description>Exposing the internals of a dinosaur-shaped children's camera I got in Shenzhen</description>
      <content:encoded><![CDATA[<html><body><p>Hello! I've recently finished a paper, so it's time to work on sillier stuff. </p>
<p>On a recent trip to China, I purchased this children's toy camera consisting of a front/back-facing camera and thermal printer which can print the captured photos on the spot. It's adorable, and I use it to take shitty analog-looking photos of my friends. </p>
<p><img alt="" class="addpic" src="/static/dinocamtoypower/0.jpeg"/></p>
<p>As someone whose only used phone cameras, having one as a monotool is pretty convenient. And the terrible picture quality is quite freeing, since no matter what you do or how you look, the picture will be pretty shite.</p>
<p>My only gripe is the speed. Most real cameras have a sleep mode where they're on, ready to shoot, but not really burning much power. I'd like to keep this camera in sleep mode too, cause it takes 5-10 seconds to turn on and another few seconds to flip around the camera. But as far as I can tell, there is no sleep mode, just a screen-off mode. You can also configure an auto-off timer in the settings, but then you have to wait for it to turn back on again.</p>
<p>If "display off" mode uses almost no power, then I can make the auto-off timer absurdly long and just turn off the display when it's in my pocket. However, if power consumption stays high, I'll just leave it off and deal with the startup time. It's time to check!</p>
<h1 id="power-metering">Power metering</h1>
<p>I've already taken this camera apart several times to re-plug in the battery, so it was easy enough to do it again to measure current consumption. I do this by attaching my oscilloscope probes across a 1Ω resistor in-line with the battery. The millivolts measured will be the milliamps consumed. </p>
<p>Side note, it's sad to me that some versions of this toy will be thrown away despite being fully functional just because the parents/kid didn't know to open it and push back the battery plug back in. It should be glued on to prevent this, but I digress. </p>
<h1 id="screen-on">Screen on</h1>
<p>After the device turns on, power consumption is a steady ~85mA on the start screen</p>
<p><img alt="" class="addpic" src="/static/dinocamtoypower/1.jpeg"/></p>
<h1 id="camera-on">Camera on</h1>
<p>With the camera on, current consumption increases to ~95mA. It does not seem to matter which camera. </p>
<p><img alt="" class="addpic" src="/static/dinocamtoypower/2.jpeg"/></p>
<p><img alt="" class="addpic" src="/static/dinocamtoypower/3.jpeg"/></p>
<h1 id="display-off">Display off</h1>
<p>When the display is off but the device is on, power consumption is still quite high. The camera seems to turn off but we are still at a steady ~80mA, nearly the same as when we're on the intro menu.</p>
<p><img alt="" class="addpic" src="/static/dinocamtoypower/4.jpeg"/></p>
<h1 id="on-shutdown">On shutdown</h1>
<p>Display off on the menu screen has a current consumption of ~62 mA. This increases as the turn-off jingle plays and the screen turns back on, but then drops to zero when the device is off (as expected) </p>
<p><img alt="" class="addpic" src="/static/dinocamtoypower/5.jpeg"/></p>
<p>Keeping the camera on the menu screen with display off uses the least power, but it's still a far cry from zero. It's a shame because I think the startup time need not be so long, and shortening it would make the camera more useful to me. I will continue to use the camera, but probably not the display-off mode. </p>
<p><img alt="" class="addpic" src="/static/dinocamtoypower/6.jpg"/></p>
<p>Cya next time!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/dinocamtoypower/</guid>
      <pubDate>Sun, 07 Apr 2024 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Apple Vision Pro Hackathon: A Retrospective</title>
      <link>https://andykong.org/blog/avph_reflection/</link>
      <description>Thoughts on the Vision Pro</description>
      <content:encoded><![CDATA[<html><body><p>If you have a Vision Pro we could borrow and live in/near Pittsburgh and are free before May 2024, I would be interested in hosting this hackathon again. Please email/DM me if you'd like that, and we can make it happen :)</p>
<hr/>
<p>Hello! Last weekend I hosted an Apple Vision Pro Hackathon (aka AVP Hackathon), which simply entailed asking everyone I knew if I could borrow a Vision Pro and then asking everyone if they wanted to develop on it for a couple days.</p>
<p><img alt="" class="addpic" src="/static/avph_reflection/0.jpg"/></p>
<p>I think hackathons are a good event for colleges since full-time coders are usually too world-weary to also spend their weekends coding. Nonetheless, I picked a bad time to ask, since I mostly knew researchers and we're a month shy of the next big conference deadline. </p>
<p>Anyway, somehow I got a crew of four people and we spent 48 hours building apps and demos for the AVP. The main goal was to write app that would make you want to wear the AVP (the most useful are screen-mirroring and web browsing). </p>
<p>We also had a rule that someone had to be wearing the AVP at all times —  in the end, this didn't even need enforcing because we were iterating so fast that someone always needed it. In the end, we made 3.5 working apps, which is a pretty good hitrate. </p>
<div class="toc"><h2 id="table-of-contents" style="margin:0px;">Table of Contents</h2>1. <a href="#day-1">Day 1 </a><br/>2. <a href="#day-2">Day 2</a><br/>3. <a href="#demos">Demos</a><br/>4. <a href="#takeaways">Takeaways</a><br/>   4.1 <a href="#real-device-stuff">Real device stuff</a><br/>   4.2 <a href="#coding-in-swift">Coding in Swift</a><br/>   4.3 <a href="#dev-community">Dev community</a><br/>   4.4 <a href="#idealogical-feedback">Idealogical feedback</a><br/>5. <a href="#conclusion">Conclusion</a><br/></div>
<hr/>
<h1 id="day-1">Day 1</h1>
<p>Started at 10am, got to the space around 10:30am. I provided beverages and coffee.</p>
<p><img alt="" class="addpic" src="/static/avph_reflection/1.jpg"/></p>
<p>We spent most of the day learning Swift. I made and loaded a custom 3D model and then tried to write custom gestures that would spawn/move it around. </p>
<p><img alt="" class="addpic" src="/static/avph_reflection/2.jpg"/></p>
<p>I cribbed the head/hand tracking from a <a href="https://github.com/FlipByBlink/HandsRuler">Hand Ruler</a> app, then kept cutting the code down until I could understand all of it. I think everyone else had a very similar process, since the default examples that Apple provides are too full-featured to just illustrate single concepts at a time. </p>
<p><img alt="" class="addpic" src="/static/avph_reflection/3.jpg"/></p>
<p>The AVP is meant to be a single-user device, so it doesn't let you do screen mirroring unless the same user is signed into both the Macbook and the AVP. In order to avoid taking off the headset every time we uploaded code, we spent a lot of time looking through the AVP cameras at our screens trying to make small edits. This is really close to the limit of what the AVP's AR resolution is capable of, and this process would've been much more comfortable if we could just project our displays into AVP space.</p>
<p>When we wrapped up the day, everyone sort-of understood the Swift structure and had run some example code already. The language is pretty straightforward, and the code completions in the Xcode IDE are more full-featured than any IDE I've used before.</p>
<h1 id="day-2">Day 2</h1>
<p>Late start, 11:30am. My roommate had some group project to work on in the morning, and everyone else woke up late.</p>
<p><img alt="" class="addpic" src="/static/avph_reflection/4.jpg"/></p>
<p>Someone else finished their teardown of an example, and made an app where the press of a button opens an immersive video. I finished wrapping up all my code into a simple demo app (<a href="https://github.com/kongmunist/Vision-Pro-Head-Hand-Tracking-Demo">on github here!</a>) and started working on custom gesture recognition. </p>
<blockquote class="twitter-tweet tw-align-center"><p dir="ltr" lang="tl">Italian simulator <a href="https://t.co/TqmlcglZLM">pic.twitter.com/TqmlcglZLM</a></p>— Andy (@oldestasian) <a href="https://twitter.com/oldestasian/status/1767012032132616565?ref_src=twsrc%5Etfw">March 11, 2024</a></blockquote>
<script async="" charset="utf-8" src="https://platform.twitter.com/widgets.js"></script>
<p>Gestures have no helper functions. Even in the [Happy Beam demo] provided by Apple, to check that the user has formed a heart-shaped hand gesture they just calculate the distance between specific hand joints and use some smoothing to make it a bit nicer looking. I wrote some functions like <code>closeTo(jointnames, threshold)</code> to make simple gestures easy to implement, then used them to detect this Italian pinched fingers gesture. </p>
<p>By ~3am, everyone had finished their app to a point where we could film it. Due to daylight savings, we thought it was an hour later.</p>
<h1 id="demos">Demos</h1>
<p>In the end, we had written the following apps: </p>
<ul>
<li>
<p>Gaussian splatting model viewer running on Metal, with hand gesture control</p>
</li>
<li>
<p>Collaborative video upload gallery for AVP/iPhone 15 spatial videos</p>
</li>
</ul>
<p><img alt="" class="addpic" src="/static/avph_reflection/riftv1.jpg"/></p>
<ul>
<li>AR interface to control the color and brightness of real-life smart bulbs</li>
</ul>
<p><img alt="" class="addpic" src="/static/avph_reflection/AVP_Light_App.jpg"/></p>
<ul>
<li>Lighter demo like the early iPod apps</li>
</ul>
<blockquote class="twitter-tweet tw-align-center" data-media-max-width="560"><p dir="ltr" lang="en">Throwback lighter app, made on the <a href="https://twitter.com/hashtag/AppleVisionPro?src=hash&amp;ref_src=twsrc%5Etfw">#AppleVisionPro</a> <a href="https://t.co/y3sJr8NvB9">pic.twitter.com/y3sJr8NvB9</a></p>— Andy (@oldestasian) <a href="https://twitter.com/oldestasian/status/1767978456908968044?ref_src=twsrc%5Etfw">March 13, 2024</a></blockquote>
<script async="" charset="utf-8" src="https://platform.twitter.com/widgets.js"></script>
<p>(I don't have all the videos yet so this list will be updated with the missing ones later)</p>
<h1 id="takeaways">Takeaways</h1>
<h2 id="real-device-stuff">Real device stuff</h2>
<ul>
<li>
<p>At first when I looked through the AVP, I compared the passthrough-AR with other AR headsets I've tried — the AVP is the best I've tried, so I enjoyed it. After a few hours of wearing/developing on the AVP, I began to compare it to no-headset and it loses on every metric. Some LED lights are flickery through the AVP camera, and reflective stuff (screens, windows) looks warped and not flat.</p>
</li>
<li>
<p>The IPD calibration (motor moving lenses closer/further) is required everytime we switched headsets, but that takes only ~10 seconds. I find it weird that I had to hit the Digital Crown button to start the IPD, but maybe this is an Apple-y design principle that nothing physical moves unless the user moves something physically. - Since we couldn't get screen mirroring working, we had to look through the AVP to see my computer screen to edit code and upload stuff. The text is surprisingly legible, but no amount of surprise overcomes the fact that it's not good enough to be comfortable. Two of the hackers couldn't read off their screen through the headset at all, and they don't even wear glasses normally. - AVP is clearly meant for a single owner — I think only 2 user profiles can be added at once. This made the hackathon a bit harder, but not impossible — for instance the eye tracking is only calibrated to one person at a time, but it's good enough that other people could use it without needing to redo the calibration.</p>
</li>
<li>
<p>The device is not socially accessible. All the experiences are kinda "in your head", requiring screen mirroring or recording to get someone else to see it. All the videos I show are shot on-device using Developer Capture in Reality Composer Pro, and a minute-long video takes ~1-2 minutes to transmit to your laptop. </p>
</li>
</ul>
<h2 id="coding-in-swift">Coding in Swift</h2>
<ul>
<li>
<p>Dev time is quick for small apps. A fresh upload required a bit more time, but each iteration of the code only took ~30 seconds from pressing the Xcode Run button to seeing the app in the headset. On my Macbook Pro with an M1 Max, build times were only ~5-10 seconds and barely noticeable.</p>
</li>
<li>
<p>It's possible to upload code through Unity and Swift, and it may be easier for you to get started if you have Unity experience. However, I did hear that there were some features which didn't exist in Unity but did in Swift, so for full features you may want to just bite the bullet and learn Swift. Swift is a great to program in once you get it. Two days after the hackathon, I actually felt an itch to write a Mac app so I could continue using the Xcode IDE. </p>
</li>
<li>
<p>Swift examples provided by Apple are incredibly full-featured, meaning they can put out <a href="https://developer.apple.com/documentation/visionos/world">four example demos</a> and show most features in use in the code. However, this is not super beginner friendly — I'd much rather have single-use example demos. </p>
</li>
<li>
<p>Examples are great for illustrating what is possible. If you see a transparent window with a floating button in the Hello World demo, you know that somewhere in the example project is the code that makes that happen.</p>
</li>
<li>
<p>Xcode is a great IDE, it has both an AVP simulator which launches separately and a containerized one that runs inside the editor. The containerized one does not show windows faithfully, meaning you should always check what they look like in the real simulator</p>
</li>
<li>
<p>The AVP simulator can pretty accurately do windows, buttons, and other functionality, so you don't even need to cough up the cash for a device if you're interested in developing more straightforward apps. The only thing it doesn't have is hands — gestures can only be developed using a real device. I remember seeing a <a href="https://varrall.substack.com/p/hand-tracking-in-vision-pro-simulator">blog post about faking hands</a> in the AVP simulator, but did not try it myself yet. </p>
</li>
</ul>
<p><img alt="" class="addpic" src="/static/avph_reflection/ide.png"/></p>
<h2 id="dev-community">Dev community</h2>
<ul>
<li>Forum posts are lacking, and very few people have posted Vision Pro development questions. If we assume 100k units have been sold, and only 10k to developers, then we can only expect ~100-1000 people active on the developer forums. Consequently, there are very few tutorials — large alpha in this space! Within a few months, you could easily become the foremost authority on VisionOS development. </li>
</ul>
<h2 id="idealogical-feedback">Idealogical feedback</h2>
<ul>
<li>I think if the AVP is meant to be a work device, then it's going to need something equivalent to hotkeys but for 3D space. Custom gestures are really cool, and they offer a lot of possibilities for new interactions (i.e. subtle hand gestures can trigger web apps or specific programs), but they can only be accessed in an Immersive App meaning no other apps can be open simultaneously, preventing window management or any fun systems-level control using custom gestures. You could make the comparison that iPhone apps are only allowed to recognize custom swipe/tap gestures when their app is open, but I think this limits the AVP much more when the available space of interactions comes from fine-grained continuous hand tracking.</li>
</ul>
<hr/>
<h1 id="conclusion">Conclusion</h1>
<p>Anyway, super fun weekend, and I'm incredibly happy that everyone got to a working demo despite starting from zero re:Swift. I'll reiterate that I'd love to do this again in April 2024, so if you have a headset or want to join next time, just reach out! Out-of-towners are welcome to stay on my couch.</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/avph_reflection/</guid>
      <pubDate>Wed, 13 Mar 2024 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Testing out the BPW34</title>
      <link>https://andykong.org/blog/testingbpw34/</link>
      <description>Tiny experiments with a tiny solar cell</description>
      <content:encoded><![CDATA[<html><body><div class="toc"><h2 id="table-of-contents" style="margin:0px;">Table of Contents</h2>1. <a href="#bpw34-description">BPW34 Description</a><br/>2. <a href="#voltage-under-no-light">Voltage under no light</a><br/>3. <a href="#voltage-under-some-light">Voltage under some light</a><br/>4. <a href="#voltage-under-lots-of-light">Voltage under lots of light</a><br/>5. <a href="#2-in-series-some-light">2 in series, some light</a><br/>6. <a href="#2-in-series-some-light-through-skin">2 in series, some light, through skin</a><br/>7. <a href="#expected-voltage-vs-skin-thickness">Expected voltage vs. skin thickness</a><br/></div>
<hr/>
<p>Hello! Did you know solar cells work even through skin? More on that later.</p>
<p>Today I'm going to show you some basic testing I did with the BPW34. This is technically a photodiode (for detection), but as we know, photodiodes work both ways.</p>
<p><img alt="" class="addpic" src="/static/testingbpw34/0.png"/></p>
<h1 id="bpw34-description">BPW34 Description</h1>
<p>These are sold on SparkFun as miniature solar cells, and I wanted to see how much power I could pull out of them in ambient light. </p>
<p><img alt="" class="addpic" src="/static/testingbpw34/1.png"/></p>
<p>The reported open-circuit voltage is 350mV, so two in series should be enough to drive an energy-harvesting PMIC. We may even get away with just using one and a dedicated solar cell harvester IC, but I didn't have any of those on hand. Also, the more the merrier!</p>
<p>I looked on Digikey for similar photodiodes (just find the BPW34 and then trace back what product category it fits into), and found plenty. There are alternatives, but none go much higher than 300mV (though they might be of photon-&gt;current higher efficiency). Watch out also, some have a daylight blocking filter which stymies their usage as solar cells.</p>
<p><img alt="" class="addpic" src="/static/testingbpw34/2.png"/></p>
<p>Here's how big it is. </p>
<p><img alt="" class="addpic" src="/static/testingbpw34/3.png"/></p>
<p>If you're using it for power harvesting, the positive terminal is the leg with a single stripe of metal (where the probe is clipped in this photo)</p>
<p><img alt="" class="addpic" src="/static/testingbpw34/4.png"/></p>
<h1 id="voltage-under-no-light">Voltage under no light</h1>
<p>I put one in a black bag just for fun and got &gt;70mV, which is not a lot, but not zero. I think this is from light leakage into the bag, but it gets an awfully small amount of light for a pretty decent voltage. You know the old saying, 5 in a bag is worth 1 in the air. I didn't try drawing power from it like this, but it should not provide very much. </p>
<p><img alt="" class="addpic" src="/static/testingbpw34/5.png"/></p>
<h1 id="voltage-under-some-light">Voltage under some light</h1>
<p>Indoors near a large window I get &gt;300mV no-load from a single diode. This varies from 290-350mV, depending on if the diode faces the window or not. </p>
<p><img alt="" class="addpic" src="/static/testingbpw34/6.png"/></p>
<h1 id="voltage-under-lots-of-light">Voltage under lots of light</h1>
<p>I blasted one with my phone flashlight and the voltage goes over 500mV. When the flashlight LED is held directly over the photodiode, I max out at 600mV. I couldn't take pictures of this since I was using my phone's flashlight, so you'll have to trust me, bro.</p>
<h1 id="2-in-series-some-light">2 in series, some light</h1>
<p>I wanted to make sure the diodes actually do add in series so I soldered two together. In ambient light they add up as expected. </p>
<p><img alt="" class="addpic" src="/static/testingbpw34/7.png"/></p>
<h1 id="2-in-series-some-light-through-skin">2 in series, some light, through skin</h1>
<p>I read <a href="https://www.sciencedirect.com/science/article/pii/S0956566316311320">this paper</a> which reported that fresh human skin transmits a lot of light in the spectrum of 700nm and up, allowing subdermal electronics to be powered by light exposure.</p>
<p><img alt="" class="addpic" src="/static/testingbpw34/8.png"/></p>
<p><img alt="" class="addpic" src="/static/testingbpw34/9.png"/></p>
<p>I did a very rudimentary test of this by covering the two diodes with my thumb. While I'm sure this is mostly leakage light from the sides of the diodes, I do get ~half the voltage.</p>
<p><img alt="" class="addpic" src="/static/testingbpw34/10.png"/></p>
<p>The paper tried this using a thin skin sample (1-2mm); my thumb is a quite a bit thicker than that so I expected way less voltage than just half. </p>
<h1 id="expected-voltage-vs-skin-thickness">Expected voltage vs. skin thickness</h1>
<p>We can compare the fresh "Upper inner arm" 1mm sample with the fresh "Forehead" 1.5mm sample — transmittance goes from ~0.56 to 0.28 at 800nm, so the relationship between transmittance and thickness is nonlinear. </p>
<p><img alt="" class="addpic" src="/static/testingbpw34/11.png"/></p>
<p>However, if we compare shoulder and forehead, we get totally different transmittances at 800nm despite the similar thickness. From <a href="https://pubmed.ncbi.nlm.nih.gov/14690333/">this paper</a>, the shoulder epidermis is 11mm thick and in <a href="https://www.selcukmedj.org/uploads/publications/2022-102-13168603.pdf">this paper</a> the forehead epidermis+dermis thickness is 0.07mm and 1.6mm. Because the shoulder sample is mostly epidermis and the forehead sample is mostly dermis, I think the dermis is doing most of the light blocking here. I conclude that the forehead (and face in general is not a great place to put implanted solar cells. </p>
<p>Anyway, that's all the testing I've done so far. Open-circuit voltage is not power, but this high of a voltage is enough for energy harvesting, however slowly that may happen. Let me know if you explore this any further!</p>
<p>Cya next time!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/testingbpw34/</guid>
      <pubDate>Mon, 04 Mar 2024 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>My Vitalia Experience</title>
      <link>https://andykong.org/blog/vitalia1/</link>
      <description>Seven days in a city trying to usurp death</description>
      <content:encoded><![CDATA[<html><body><p>Hello. I just got back from Vitalia, a pop-up city in Prospera, a special economic zone (ZEDE) on Roatán, an island which fields a lot of cruise ship tourists and is part of the nation of Honduras, a country consisting of AT LEAST 99 islands. </p>
<p><img alt="" class="addpic" src="/static/vitalia1/0.png"/></p>
<p>I'm going to show some highlights and work through a "should I go to Vitalia-like thing in the future" scenario with you.</p>
<p>Generally, Vitalia felt like a vacation amongst like-minded people. I came during an off-week, so there wasn't a ton of networking or talks going on, but this gave me more time to have good conversations with people with mutual rare interests. I think it's worth going for an on-week, but off-week it's just a very casual gathering + vacation.</p>
<hr/>
<h1 id="highlights">Highlights</h1>
<h2 id="people">People</h2>
<ul>
<li>
<p>It surprised me that many people were in their 30s and 40s — I suppose this is when the "I'm gonna die but I'm having too much fun rn" urge kicks in? Towards the end, a few undergraduate students flew in for the conference week, but I did not meet anyone near my age. </p>
</li>
<li>
<p>I came to Vitalia looking for fellow builders. Without counting business builders, these are a rare breed at Vitalia — I'd say only 4 out of the ~50 people I met. I broke in the soldering iron in the Augmentation Lab (first!!), and I came in the 6th week of Vitalia being open.</p>
</li>
</ul>
<p class="caption"> Underutilized electronics workshop at Vitalia </p>
<p><img alt="Underutilized electronics workshop at Vitalia" class="addpic" src="/static/vitalia1/2.jpeg"/></p>
<ul>
<li>There are lots of Quantified Self people, nootropics people, and implant people. These fit my interests pretty well, so I enjoyed talking to these people — I've never found as dense a population as Vitalia. While I was there, the Biohacker DAO ran an intranasal insulin vs. cognition study and let anyone try their hand at analyzing the data. My roommate was into nootropics, and had purchased and tried many of the compounds I asked him about. And the Augmentation Lab, ran by Cassox of <a href="https://augmentationlimitles.ipage.com/">Augmentation Limitless</a> always had a couple cyborgs hanging out that would entertain fun conversations on transhumanism. </li>
</ul>
<p class="caption"> Cognitive enhancement / longevity substances table. Only legal stuff of course, and a ton of olive oil. </p>
<p><img alt="Cognitive enhancement / longevity substances table. Only legal stuff of course, and a ton of olive oil." class="addpic" src="/static/vitalia1/3.jpeg"/></p>
<ul>
<li>
<p>There were also many crypto people. During my stay, there was some kind of fundraiser on Gitcoin for Vitalia which used "quadratic funding" to match donations, and several people were fully interested in making useful cryptocurrencies. I find it hard to talk to those people, but I did like seeing real-world use cases for cryptocurrency. Even then, I thought many of the Gitcoin projects were stupid — IMO, creating some dashboard or display or status token should not require much crowdfunding.</p>
</li>
<li>
<p>Everyone knows everything about Bryan Johnson and his penis. His olive oil takes up half the medicine table — I ate that for lunch most days since it was free. It leaves a spicy feeling in the throat. Since I never drink other olive oil straight, I don't have anything to compare it to, but I did feel satiated afterwards. In high school I tried a tablespoon of medium chain triglycerides (MCTs, fancy oil for your brain promoted by BulletProof coffee guy), which left me on the toilet for a few painful hours — trying the same with olive oil set off warning bells in my head, but nothing happened. </p>
</li>
<li>
<p>Since I went for an off-week, not many short-term people were around and there weren't many planned events. Consequently, I saw everyone going into the gym at least once, every day. Even I went, spurred by my roommate to train just a muscle group a day. We all know how effectively these gateway drugs work, and soon I was spending an hour a day lifting smelly heavy objects with him. Please don't think I'm complaining — I enjoy feeling sore and working out, but weights are always smelly and usually heavy. </p>
</li>
</ul>
<p class="caption"> One of the talks, History of Cybernetics, hosted by Cass and Fraiz </p>
<p><img alt="One of the talks, History of Cybernetics, hosted by Cass and Fraiz" class="addpic" src="/static/vitalia1/4.png"/></p>
<ul>
<li>Most longevity people focused on genetic stuff, altering aging or improving cognition at the DNA level (I think this approach comes from watching Aubrey de Grey?). I find this approach a bit long-winded — highly skeptical that simply switching a fetus's SNPs to the slightly higher IQ ones would lead to any benefit in intelligence without causing some awful incidental genetic mutations. Price of progress or avoidable folly?</li>
</ul>
<h2 id="honduras">Honduras</h2>
<p><img alt="" class="addpic" src="/static/vitalia1/5.jpg"/></p>
<h3>Climate</h3>
<ul>
<li>
<p>Man, I've lived somewhere cold for the past 6 years (Pittsburgh then Boston then SF then Seattle then Chicago then Zurich) — it is NICE to go outside wearing only a t-shirt. And pants and stuff, but I mean the one-thin-layer thing. Very comfy aside from the sweat and the AC being too cold sometimes. </p>
</li>
<li>
<p>Oh yea, and I forgot there are also a lot of bugs in warm places. My first night I thought "oh it's so warm, let's sleep on the patio" — woke up when it started pouring, and noticed like 30-some bug bites all over my feet. I had been warned of the sand flies, but I looked with my flashlight whenever I felt something and never saw anything. My roommate referred to these creatures as "no-see-ems".</p>
</li>
<li>
<p>It is beautiful on the island. Birds are making calls I've never heard (there's one that does a proper <a href="https://en.wikipedia.org/wiki/Chirp">chirp</a> like the signals concept), coral reefs that you can just kayak to and look at, and crazy tropical fish. It's interesting to me that fish can be all sorts of gaudy neon colors while birds are advised to be more camoflagued. Also, I didn't notice sexual dimorphism in the fish, which is definitely a phenomena in many bird species (I remember reading that male ducks are shinier so they can distract predators from the female ducks). Possibly this is because the mother fish is not needed for the fish babies to survive, unlike in birds, so fish don't if one sex of adults takes the hit more often.</p>
</li>
</ul>
<p class="caption"> Weird pirates everywhere in my hotel </p>
<p><img alt="Weird pirates everywhere in my hotel" class="addpic" src="/static/vitalia1/6.jpg"/></p>
<h3>Food</h3>
<ul>
<li>Food is not cheap. At the resort you have an option of three places, and all the food is... dry? This is the word I'm going with. I felt like all the food I ate parched me. Their rice contains less water than I'm used to, beans are dry, meat is usually dry, fish (fried) is dry. The last day I had a pasta with lobster, that meal really felt good and wet.</li>
</ul>
<p class="caption"> We have the same ramen at home, except not the shrimp flavored one. I bought one of those to try </p>
<p><img alt="We have the same ramen at home, except not the shrimp flavored one. I bought one of those to try" class="addpic" src="/static/vitalia1/7.jpeg"/></p>
<ul>
<li>The grocery store nearest my hotel stocked a lot of American goodies, possibly for the tourists to feel at home. I saw several Kirkland branded things, and many familiar canned goods. This kinda took away the appeal of shopping at a local store, but I did find a few items to enjoy. Particularly, this brand of "drinkable" yogurt. The yogurt is basic cup-yogurt consistency in a large water bottle form-factor, and it's delicious though hard to drink. Prices mostly match US groceries. </li>
</ul>
<p><img alt="" class="addpic" src="/static/vitalia1/8.jpeg"/></p>
<h3>Random</h3>
<ul>
<li>
<p>On Roatán, transportation is spotty (flag a taxi and haggle, or flag the bus and haggle, or walk) and WiFi is mid (people complained they couldn't videoconference). I experienced my first rotten egg while cooking one night, which stank up the cooking area and temporarily took my pan out of commission. I float-tested the rest of the eggs and set aside any that even stood up in the water. Luckily I only had one bad egg, and most of the standing ones were ok too (just yolk disintegrated instead of in one blob)</p>
</li>
<li>
<p>Because there is so much tourism, everyone takes payment in USD but gives change in Honduran Limperas so you WILL get a chance to collect bills as souvenirs. No coins due to 1 Limpera = 4 cents</p>
</li>
</ul>
<h1 id="longer-term">Longer term</h1>
<ul>
<li>
<p>I acquired a long reading list through my conversations that I need to work through — I imagine this will be arduous scientific stuff. Some people recommended me various longevity supplements which I'll read more about. </p>
</li>
<li>
<p>I met the implant folks at AugLim, showed them my <a href="../../projects/implantables">demo</a> and joined their Discord group. Hopefully we'll be able to work together synergistically in the future once I get the power delivery sorted out.</p>
</li>
<li>
<p>Quantified Self people made a nice group chat, this will be a neat place to post personal analyses (maybe?) and get feedback or inspiration on self-experiments</p>
</li>
<li>
<p>I got a lot of SF connections so hopefully finding a group house will be easier. </p>
</li>
<li>
<p>Several people asked me to help their project with some electronics, this will fund the Year further. </p>
</li>
<li>
<p>I'm now considering incorporating just so what I'm doing is more legible to other people. When I tell people I'm taking a gap to work on personal projects, sometimes I see them assume I'm just doing nothing and having fun. But that's not the case — I am working hard on stuff I think is useful, I'm just not employed and this is not a startup and it's not a research project. Open-endedness is difficult for people to understand, and even harder for me to convey. So maybe startup? </p>
</li>
<li>
<p>Oh, and I made a great friend :)</p>
</li>
</ul>
<p class="caption"> My new friend James </p>
<p><img alt="My new friend James" class="addpic" src="/static/vitalia1/9.jpg"/></p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/vitalia1/</guid>
      <pubDate>Fri, 23 Feb 2024 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Timezone correcting Fitbit sleep data using Google Timeline</title>
      <link>https://andykong.org/blog/glocfitbittzcorrection/</link>
      <description>My last post on dealing with terrible Fitbit data</description>
      <content:encoded><![CDATA[<html><body><p>Hello friends, I purchased an Apple Watch and (hopefully) escaped from the hellhole that is the Fitbit data environment — forever!</p>
<blockquote class="twitter-tweet" data-media-max-width="560"><p dir="ltr" lang="en">Custom Apple Watch strap that turns it into a tilted driver watch <a href="https://t.co/Ktp1nmskyw">pic.twitter.com/Ktp1nmskyw</a></p>— Andy (@oldestasian) <a href="https://twitter.com/oldestasian/status/1756578851893760155?ref_src=twsrc%5Etfw">February 11, 2024</a></blockquote>
<script async="" charset="utf-8" src="https://platform.twitter.com/widgets.js"></script>
<p>To cap off all my Fitbit posting, I exported my data one last time and noticed that they have restructured the data and ruined my <a href="../fitbitsleeptzcorrection">stress method</a> for timezone correction, rendering all my latest data unusable. Since I've collected more Tetris and Stroop data that I wanted to correlate, I decided to try one final method that I've always thought about but never cared enough to do — syncing my Google Location with Fitbit's data to get UTC timestamps. I'll describe the steps verbally and the code will be at the end. </p>
<h1 id="method">Method</h1>
<p>First I loaded my Google location data with <a href="https://github.com/Scarygami/location-history-json-converter">this neat tool</a> by Scarygami which produces a massive CSV of all the longitude and latitude and everything else. Here I visualized part of it using plotly's <code>scatter_mapbox</code> function. </p>
<p><img alt="" class="addpic" src="/static/glocfitbittzcorrection/0.png"/></p>
<p>From each longitude and latitude, you can get the timezone name using the Python library <a href="https://pypi.org/project/timezonefinder/">timezonefinder</a>, which works at a rate of ~2ms/1000 coordinates without needing to do GET requests. I converted the timezone strings into timezone offsets using some code from <a href="https://stackoverflow.com/questions/5537876/get-utc-offset-from-time-zone-name-in-python">this SO post</a>. </p>
<p>Then I realized that I needed to also account for daylight savings time across two regions, Europe and America, which for some reason have DST dates that are about a week off from each other. I can't wait for them to abolish DST so I'll have to add a bool which determines if DST correction is necessary in my timezone correction pipeline /s</p>
<p><img alt="" class="addpic" src="/static/glocfitbittzcorrection/1.png"/></p>
<p>Some sleep sessions have no "close" longitude/latitude coordinates, so I toss them. Close is defined arbitrarily, I use 6 hrs as my cutoff. </p>
<p>My success rate with this method is ~60%, and most of the loss comes from me neglecting to turn on Google Timeline on my iPhone until sometime mid-2021. In total, I recovered 600+ days of <em>accurate</em> Fitbit sleep data and can now produce fun sleep graphs which I can trust. For instance, here is my true Tetris speed vs. sleep graph: </p>
<p><img alt="" class="addpic" src="/static/glocfitbittzcorrection/2.png"/></p>
<p>Turns out sleep is good for you, actually.</p>
<h1 id="code">Code</h1>
<p>Code is provided with no guarantees or user-friendly comments. Use at your own risk, etc. etc.</p>
<script src="https://gist.github.com/kongmunist/a4945c339b11d4e953e5e806344e42c8.js"></script></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/glocfitbittzcorrection/</guid>
      <pubDate>Mon, 12 Feb 2024 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>A computer scientist's guide to I2C</title>
      <link>https://andykong.org/blog/attinyrtc/</link>
      <description>Illustrated via minimal example of ATtiny85 to DS3231 communication using TinyWireM</description>
      <content:encoded><![CDATA[<html><body><p>Hello! I am continuing on my <a href="../ard2attiny">last post</a>, this time we're making an ATtiny do I2C. </p>
<p>As a CS person, I didn't really need to learn how to do I2C since every chip I've used before came on a breakout board and had a nice GitHub library I could steal. Eventually I needed to learn it myself to use cooler chips, and the post you're reading now is the post I wish I had when I started learning how I2C works.</p>
<p><a href="https://thecavepearlproject.org/2017/11/03/configuring-i2c-sensors-with-arduino/">This post</a> from the Cave Pearl Project initially helped me understand I2C, but it assumes a bit more knowledge than this one.</p>
<h1 id="i2c-is-like-an-api-for-ics">I2C is like an API for ICs</h1>
<p>If you've ever worked with big Python libraries with unwieldy documentation and a million functions, it may help you to think of I2C as a similarly big library for hardware. Specifically, I'm thinking of matplotlib. You read and write options using matplotlib functions, you pass data, magical stuff happens internally, and voila, graph! And if it breaks, debugging is confusing because the functionality that breaks is usually buried deep in the library. </p>
<p><img alt="" class="addpic" src="/static/attinyrtc/1.jpg"/></p>
<p>I2C has similar functionality, except instead of calling a nice English function like <code>plt.clear()</code>, you need to say "write 0x1 to the SHUTDOWN register at position 0x10". Chips using I2C have a bunch of data and config registers. To access a sensor reading, you say "let me have 8 bytes from register XX". To configure sensor settings, you say "write XX into register YY". This makes it a bit more annoying since register addresses are harder to memorize than function names in English. You will usually be checking the datasheet a lot.</p>
<p>Normally you need to instruct the microcontroller to bit-shift your message out as bytes, but the Arduino Wire.h library makes this easier by providing functions like <code>Wire.write(0xFE)</code>.</p>
<p class="caption"> Example table of the register locations from an I2C sensor datasheet </p>
<p><img alt="Example table of the register locations from an I2C sensor datasheet" class="addpic" src="/static/attinyrtc/0.png"/></p>
<p class="caption"> Example table describing what the bytes mean in a particular register </p>
<p><img alt="Example table describing what the bytes mean in a particular register" class="addpic" src="/static/attinyrtc/2.png"/></p>
<p>Also unlike software, hardware functions cannot be called willy-nilly. Since multiple I2C chips can share the data line, each chip expects the I2C master to say "Hey, I'm gonna write to device address 0x43" or "Hey, I'm gonna read from device address 0x44" before reading or writing. The reads and writes are called "transmissions", so these start and stop messages are called <code>beginTransmission</code> and <code>endTransmission</code>.</p>
<p class="caption"> An example I2C transmission which tells the sensor which register to send data from </p>
<p><img alt="An example I2C transmission which tells the sensor which register to send data from" class="addpic" src="/static/attinyrtc/3.png"/></p>
<p>Since each I2C chip comes with a pre-programmed address, if two of the same device share a data line, you will not be able to communicate with them separately. This is why I2C chips usually come with a shutdown pin, so the main microcontroller can turn off all except one to say "Device 0x43, you are now called device 0x45" to talk to them separately when they're all turned on. </p>
<h1 id="ok-get-on-with-the-example">Ok, get on with the example!</h1>
<p>Most popular I2C chips come on a breakout board, and this usually guarantees the existence of at least one GitHub library that makes talking to the sensor easier. These libraries turn "hey device 0x43, read 8 bytes from register 0x42, end transaction" into a handy function like <code>readSensor()</code>. But sometimes, they don't have it. Other times, you want to learn how it works. </p>
<p class="caption"> Pic from the last post </p>
<p><img alt="Pic from the last post" class="addpic" src="/static/ard2attiny/0.jpeg"/></p>
<p>I recently got an ATtiny working on a breadboard using an Arduino Uno as the programmer. Afterwards, I also wanted to use an I2C RTC chip (real-time clock) with the ATtiny — while there are I2C libraries for it, they are written for the Arduino. And since the ATtiny25/45/85 doesn't have hardware I2C support, the <code>Wire.h</code> library that eases I2C transactions doesn't work, so the RTC libraries need a bit of editing. The ATtiny uses the <a href="https://github.com/adafruit/TinyWireM">TinyWireM.h</a> library instead as a (basically) drop-in replacement, you should be able to just replace all <code>Wire</code> with <code>TinyWireM</code>.</p>
<p>I really wanted to manually understand the I2C stuff, so I found a DS3231 (aka ZS-042) minimal example on the <a href="https://forum.arduino.cc/t/software-i2c-and-ds3231-simple-code/508288">Arduino forums</a> and converted it to use TinyWireM. On the ATtiny, the I2C pins are SDA on physical pin 5 and SCL on physical pin 7 (these are also known as PB0 and PB2 respectively). </p>
<p><img alt="" class="addpic" src="/static/attinyrtc/4.png"/></p>
<p>I've added SoftwareSerial so we can receive the ATtiny messages using an FTDI Friend, its RX should go into physical pin 3 (same as my previous post). Wires from the ZS-042 board are SCL -&gt; pin 7 and SDA -&gt; pin 5. V+ and GND also need to be connected. </p>
<p class="caption"> Wiring </p>
<p><img alt="Wiring" class="addpic" src="/static/attinyrtc/5.jpg"/></p>
<p>Here is the ATtiny code. It's simple enough to read and understand, and you can compare it to the <a href="https://www.analog.com/media/en/technical-documentation/data-sheets/ds3231.pdf">datasheet</a> to understand what registers are being written to and what the intended purpose is.</p>
<script src="https://gist.github.com/kongmunist/fb0f0ba41522a056364a2c41e3f1b07e.js"></script>
<p>I'm using the Arduino to flash the ATtiny. After flashing, I had to unplug the programming wires from the ATtiny's physical pins 5, 6, 7 or else they interfere with the SDA/SCL messages from the RTC and you get a message like "Time=00:00:00(bad)".</p>
<p class="caption"> What your serial monitor should NOT look like </p>
<p><img alt="What your serial monitor should NOT look like" class="addpic" src="/static/attinyrtc/6.png"/></p>
<p>Here's what the Serial monitor should look like when it's working properly:</p>
<p class="caption"> Yay, free time </p>
<p><img alt="Yay, free time" class="addpic" src="/static/attinyrtc/7.png"/></p>
<h1 id="conclusion">Conclusion</h1>
<p>It is annoying and tedious to read the 50-page datasheet and translate all the register addresses and values into functions that can be used for your application, but it is the only way. I got into I2C hoping there was an easier way to convert the datasheet into usable functions, but much like using matplotlib, the easiest way is through. At least we can be thankful for the Wire.h and TinyWireM.h authors who have written the even lower-level functions for us.</p>
<p>Cya next time!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/attinyrtc/</guid>
      <pubDate>Mon, 05 Feb 2024 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Programming an ATtiny85V using an Arduino</title>
      <link>https://andykong.org/blog/ard2attiny/</link>
      <description>Blink &amp; SoftwareSerial &amp; ADC, oh my!</description>
      <content:encoded><![CDATA[<html><body><p>Hello! Today I'm gonna walk you through uploading code to an ATtiny using an Arduino. This was my first time programming a microcontroller off-board, and I got most of the process following <a href="https://www.youtube.com/watch?v=TUlzOD9T3nI">this video</a>. But that video was not sufficient alone, so I'll be describing some of the extra steps in this post.</p>
<p><img alt="" class="addpic" src="/static/ard2attiny/0.jpeg"/></p>
<h1 id="ingredients">Ingredients</h1>
<p>You'll need an ATtiny25/45/85 chip, Arduino, 6 jumper wires, a resistor + LED, and maybe a 10uF capacitor. </p>
<h1 id="prepping-the-arduino">Prepping the Arduino</h1>
<p>First we must flash the Arduino with the ArduinoISP sketch (found in File-&gt;Examples-&gt;11.ArduinoISP). We are using the "old style" wiring, so also uncomment line 81 in this sketch. </p>
<p><img alt="" class="addpic" src="/static/ard2attiny/1.png"/></p>
<p>We're not going to program the Arduino again, so go ahead and switch the target board to "ATtiny25/45/85" in Tools. If you don't have this option in your boards, follow the instructions in the <a href="https://github.com/SpenceKonde/ATTinyCore/blob/v2.0.0-devThis-is-the-head-submit-PRs-against-this/Installation.md">ATtiny repo</a>. </p>
<p>Change the programmer to "Arduino as ISP" as in the image below, and we'll be ready to go. </p>
<p><img alt="" class="addpic" src="/static/ard2attiny/2.png"/></p>
<h1 id="wiring-the-attiny">Wiring the ATtiny</h1>
<p>Now we're going to need those jumpers. Follow this wiring diagram from the video above so we can upload code to the ATtiny</p>
<p class="caption"> [source](https://www.youtube.com/watch?v=TUlzOD9T3nI) </p>
<p><img alt="source" class="addpic" src="/static/ard2attiny/3.jpeg"/></p>
<p>To do the Blink sketch properly, we need to add the LED and resistor across PB0 and GND. Make sure the longer (positive) leg of the LED is on PB0. </p>
<p><img alt="" class="addpic" src="/static/ard2attiny/4.jpeg"/></p>
<h1 id="uploading-blink">Uploading Blink</h1>
<p>If this is the first time your ATtiny is being used or you've changed any build settings, you'll need to hit Tools-&gt;Burn Bootloader. This should work with no errors, but if you get one, make sure you've uncommented that "old style wiring" line in ArduinoISP and that your wire connections are solid. </p>
<p>Now, open the Blink example sketch. Change all instances of <code>LED_BUILTIN</code> to 0, then upload the sketch. If you have no errors, fortune smiles upon thee. Proceed to the next section. </p>
<p>If you instead get an error like <code>avrdude: stk500_paged_write(): (a) protocol error, expect=0x14, resp=0x10</code>, you'll need to place a &gt;10uF capacitor across the RESET and GND pins of the Arduino to temporarily disable the RESET button. Put it in and hit upload again, and your ATtiny should start to blink. Woohoo!</p>
<h1 id="uploading-softwareserial">Uploading SoftwareSerial</h1>
<p>To test any non-visual functionality, we're going to want to print debug messages over Serial. While we can't do Serial in the ATtiny hardware, we can include the SoftwareSerial library and do it anyway. The following code is derived from an example on this <a href="https://www.instructables.com/ATtiny85-ATtiny84-Analog-Pins-Serial-Communication/">guide</a>, but you can use the sketch in Examples-&gt;SoftwareSerial also. </p>
<p class="caption"> SoftwareSerial example works, though the pin numbers are all new to me </p>
<p><img alt="SoftwareSerial example works, though the pin numbers are all new to me" class="addpic" src="/static/ard2attiny/5.png"/></p>
<h2 id="sus-method">Sus method</h2>
<p>If the ATtiny uses SoftwareSerial, you can route the TX physical pin to the Arduino's TX pin and open the Serial monitor. You should see random noise characters, but if you hold down the RESET button, you can get messages from the ATtiny to show up.</p>
<p class="caption"> Fine for words, but holding down the RESET messed with my ADC values. </p>
<p><img alt="Fine for words, but holding down the RESET messed with my ADC values." class="addpic" src="/static/ard2attiny/6.png"/></p>
<p>The ATtiny's messages are scrambled when we receive them over Serial because the Arduino also uses the RX/TX pins and they're physically routed to the UART-&gt;USB converter chip. By holding down the RESET, we disable the Arduino and get to see the ATtiny messages without any problems.</p>
<h2 id="less-sus-method">Less sus method</h2>
<p>The proper way is to get an FTDI Friend or other UART-&gt;USB converter and receive messages using that instead. </p>
<p><img alt="" class="addpic" src="/static/ard2attiny/7.jpeg"/></p>
<p>If you go this route, the ATtiny's TX goes into the FTDI Friend's  RX, and the GNDs are connected. The port will have to be changed, and the messages will be received just fine. </p>
<p><img alt="" class="addpic" src="/static/ard2attiny/8.jpeg"/></p>
<h1 id="trying-out-the-adc">Trying out the ADC</h1>
<p>The ATtiny85 is big enough to fit a lot of the Arduino helper functions, so the <code>analogRead</code> function just works like normal. You will need to be careful to pick a pin which can receive analog, so be sure to check. Here's my code to read from pin 2 and print it to Serial. </p>
<!-- HTML generated using hilite.me -->
<div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%"><span style="color: #557799">#include "SoftwareSerial.h"</span>
<span style="color: #008800; font-weight: bold">const</span> <span style="color: #333399; font-weight: bold">int</span> LED <span style="color: #333333">=</span> <span style="color: #0000DD; font-weight: bold">0</span>; 
<span style="color: #008800; font-weight: bold">const</span> <span style="color: #333399; font-weight: bold">int</span> ANTENNA <span style="color: #333333">=</span> <span style="color: #0000DD; font-weight: bold">3</span>;
<span style="color: #008800; font-weight: bold">const</span> <span style="color: #333399; font-weight: bold">int</span> Rx <span style="color: #333333">=</span> <span style="color: #0000DD; font-weight: bold">2</span>; 
<span style="color: #008800; font-weight: bold">const</span> <span style="color: #333399; font-weight: bold">int</span> Tx <span style="color: #333333">=</span> <span style="color: #0000DD; font-weight: bold">4</span>;
SoftwareSerial <span style="color: #0066BB; font-weight: bold">mySerial</span>(Rx, Tx);
<span style="color: #333399; font-weight: bold">int</span> valu <span style="color: #333333">=</span> <span style="color: #0000DD; font-weight: bold">0</span>; <span style="color: #888888">// variable to store antenna readings</span>

<span style="color: #333399; font-weight: bold">void</span> <span style="color: #0066BB; font-weight: bold">setup</span>(){
    pinMode(LED, OUTPUT); <span style="color: #888888">// tell Arduino LED is an output</span>
    pinMode(ANTENNA, INPUT);
    pinMode(Rx, INPUT);
    pinMode(Tx, OUTPUT);
    mySerial.begin(<span style="color: #0000DD; font-weight: bold">4800</span>); <span style="color: #888888">// send serial data at 9600 bits/sec</span>
}

<span style="color: #333399; font-weight: bold">void</span> <span style="color: #0066BB; font-weight: bold">loop</span>() {
    valu <span style="color: #333333">=</span> analogRead(ANTENNA); <span style="color: #888888">// read the ANTENNA</span>
    mySerial.println(valu); <span style="color: #888888">// send the value to Serial Monitor, ^Cmd-M</span>
    digitalWrite(LED, HIGH); <span style="color: #888888">// turn LED ON</span>
    delay(<span style="color: #0000DD; font-weight: bold">10</span>); digitalWrite(LED, LOW); <span style="color: #888888">// turn off</span>
}
</pre></div>
<p class="caption"> ADC readings plotted over time </p>
<p><img alt="ADC readings plotted over time" class="addpic" src="/static/ard2attiny/9.png"/></p>
<h1 id="have-fun">Have fun!</h1>
<p>Hopefully you know the basics. As <a href="https://www.youtube.com/watch?v=7bZg_GzUbHI&amp;t=1771s">DeepBlueMbedded</a> said, you're basically done once you get blinking.</p>
<p><img alt="" class="addpic" src="/static/ard2attiny/10.png"/></p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/ard2attiny/</guid>
      <pubDate>Fri, 02 Feb 2024 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>My Quantified-Self Data Stack</title>
      <link>https://andykong.org/blog/allmydata/</link>
      <description>Many cool self-insights and the data logging methods used</description>
      <content:encoded><![CDATA[<html><body><div class="toc"><h2 id="table-of-contents" style="margin:0px;">Table of Contents</h2>1. <a href="#why-i-collect-data">Why I collect data</a><br/>2. <a href="#cool-selfinsights-ive-found">Cool self-insights I've found</a><br/>3. <a href="#my-data-stack">My data stack</a><br/>   3.1 <a href="#browser-activity">Browser Activity</a><br/>   3.2 <a href="#hard-drive-space">Hard Drive Space</a><br/>   3.3 <a href="#physical-location">Physical Location </a><br/>   3.4 <a href="#smartwatch-biometrics">Smartwatch Biometrics</a><br/>   3.5 <a href="#music">Music</a><br/>   3.6 <a href="#tweets">Tweets</a><br/>   3.7 <a href="#purchasing-data">Purchasing data</a><br/>   3.8 <a href="#glucose">Glucose</a><br/>   3.9 <a href="#air-quality">Air quality</a><br/>   3.10 <a href="#tetris-cognitive">Tetris (cognitive)</a><br/>   3.11 <a href="#stroop-cognitive">Stroop (cognitive)</a><br/>   3.12 <a href="#weight-and-grip-strength">Weight and grip strength</a><br/>   3.13 <a href="#dose-times">Dose times</a><br/>4. <a href="#conclusion">Conclusion</a><br/>   4.1 <a href="#do-it-now">Do it now!</a><br/>   4.2 <a href="#suggestions">Suggestions?</a><br/></div>
<hr/>
<p>If you've ever wondered if you sleep worse after drinking coffee in the morning or how listening to music affects your focus, this post is for you. I used to have these musings, and now I just have the data to check for myself. </p>
<p>In this post I'm going to show y'all few self-insights I've derived from logging a lot of my personal data. I'll also list all the types of data and how they're collected. </p>
<h1 id="why-i-collect-data">Why I collect data</h1>
<p>I think it's important to specify "why research" as well as "how research", and this "why" can become insincere when scientists court the big grant-writers in industry and government instead of saying what they really mean. I currently work for myself and will try to be a bit more honest — I like learning how my body and mind interact with my environment to create my lived experience. I also just like to know stuff.</p>
<p><img alt="" class="addpic" src="/static/allmydata/0.png"/></p>
<h1 id="cool-selfinsights-ive-found">Cool self-insights I've found</h1>
<ul>
<li>My Ritalin blood concentration correlates with better Tetris performance</li>
</ul>
<p><img alt="" class="addpic" src="/static/allmydata/1.png"/></p>
<ul>
<li>I think better with more sleep (actually one of my most burning questions)</li>
</ul>
<p><img alt="" class="addpic" src="/static/allmydata/2.png"/></p>
<ul>
<li>My Tetris speed (pieces per second = PPS) correlates slightly with lower humidity</li>
</ul>
<p><img alt="" class="addpic" src="/static/allmydata/3.png"/></p>
<p>Here's a <a href="https://medium.com/@kongmunist/playing-faster-tetris-by-sleeping-less-3d9b04d30349">few</a> more <a href="/blog/stroopvssleep/">examples</a></p>
<hr/>
<h1 id="my-data-stack">My data stack</h1>
<p>Data that's useful for my analysis is either physical (heart rate, blood glucose, weight), mental (Tetris, Stroop), or environmental (air quality, music, location, computer activity). Data collection can be easy (hard drive space, Google location), or hard (computer activity), or just cost money (Fitbit, Spotify)</p>
<p>Not all the data is obviously useful — some of it is just easy to collect. But most data I expect to use as an 'x' or 'y' variable in some future analysis.</p>
<h2 id="browser-activity">Browser Activity</h2>
<p>Chrome websites visited with URL and timestamp. Collected via Tom Critchlow's <a href="../settingupelectrictables/">JS electric tables script</a></p>
<p><img alt="" class="addpic" src="/static/allmydata/4.png"/></p>
<h2 id="hard-drive-space">Hard Drive Space</h2>
<p>Every 4 hours, a bash script logs my available hard drive space in bytes. </p>
<p><img alt="" class="addpic" src="/static/allmydata/5.png"/></p>
<h2 id="physical-location">Physical Location</h2>
<p>Google Maps Timeline is enabled on my phone and records my latitude/longitude every few minutes. I have this data going back a couple of years, and it includes the velocity and predicted mode of transport. </p>
<p><img alt="" class="addpic" src="/static/allmydata/6.png"/></p>
<h2 id="smartwatch-biometrics">Smartwatch Biometrics</h2>
<p>Fitbit records a ton of data and multiple granularities. There's tons of problems with the data itself (documented <a href="../fitbittsproblem/">here</a> and <a href="../fitbitsleeptzcorrection/">here</a> ), but the majority is usable. Most usable stuff is probably the nightly sleep stats, minutely heart rate + HR variance, and daily step count. Fitbit offers this data export <a href="https://www.fitbit.com/settings/data/export&lt;/a&gt;">through the web interface</a></p>
<p><img alt="" class="addpic" src="/static/allmydata/7.png"/></p>
<h2 id="music">Music</h2>
<p>Spotify! It's a bit complicated and I haven't looked into it, but all the streaming data is there. Spotify offers this data export <a href="https://support.spotify.com/us/article/understanding-my-data/">through the web interface</a></p>
<p><img alt="" class="addpic" src="/static/allmydata/8.png"/></p>
<h2 id="tweets">Tweets</h2>
<p>Twitter activity, has been used by <a href="https://twitter.com/ultimape/status/1145889385256296449&lt;/a&gt;">ultimape</a> to detect their shifting circadian rhythm, has been used by Andy for nothing so far. Twitter offers this data export <a href="https://help.twitter.com/en/managing-your-account/how-to-download-your-x-archive&lt;/a&gt;">through the web interface</a></p>
<p><img alt="" class="addpic" src="/static/allmydata/9.png"/></p>
<h2 id="purchasing-data">Purchasing data</h2>
<p>My Apple Card tracks all my purchases. The data is retrieved by manually hitting the "Export CSV" button on each month, and includes the Merchant and category and date.</p>
<p><img alt="" class="addpic" src="/static/allmydata/10.png"/></p>
<h2 id="glucose">Glucose</h2>
<p>I wore a Freestyle Libre 2 CGM for two weeks and have nearly continuous data from that period (15-min intervals). Export is done through web interface.</p>
<p><img alt="" class="addpic" src="/static/allmydata/11.png"/></p>
<h2 id="air-quality">Air quality</h2>
<p>I use a QingPing air quality monitor to track my relative humidity, temperature, CO2, particulates (PM2.5), and tVOC (total volatile organic compounds). Through the QingPing IoT app, we can export the past year of data logged at 15-min intervals. </p>
<p><img alt="" class="addpic" src="/static/allmydata/12.png"/></p>
<h2 id="tetris-cognitive">Tetris (cognitive)</h2>
<p>I play Tetris for fun a few times a day. The website I play on records the stats and timestamps of each game, and I wrote a little Selenium scraper to retrieve it all. I run this every few months to collate all the data into a CSV.</p>
<p><img alt="" class="addpic" src="/static/allmydata/13.png"/></p>
<h2 id="stroop-cognitive">Stroop (cognitive)</h2>
<p>I use my Strooper <a href="../../projects/strooper">chrome extension</a> and complete a mini-Stroop test a few times a day. This gives me a coarse metric of my executive function, reading speed, and reaction time randomly throughout the day. I transfer the results into a spreadsheet monthly. </p>
<p><img alt="" class="addpic" src="/static/allmydata/14.png"/></p>
<p><img alt="" class="addpic" src="/static/allmydata/15.png"/></p>
<h2 id="weight-and-grip-strength">Weight and grip strength</h2>
<p>Every night I record my weight and fat % with a bioimpedance scale, then record my left and right handed grip strength using a dynamometer. I record these as calendar events and transfer them to spreadsheets monthly. </p>
<p><img alt="" class="addpic" src="/static/allmydata/16.png"/></p>
<h2 id="dose-times">Dose times</h2>
<p>I frequently drink caffeinated beverages, sometimes use nicotine patches, and randomly try out OTC supplements like L-Theanine or magnesium. I update a calendar notebook with my dosage and time, and have gotten pretty good at just noting the time when I take them and logging it later. I transfer this to a spreadsheet every month or so. </p>
<p><img alt="" class="addpic" src="/static/allmydata/17.png"/></p>
<hr/>
<h1 id="conclusion">Conclusion</h1>
<h2 id="do-it-now">Do it now!</h2>
<p>Collecting personal data for long periods of time has compounding returns — you'll need to gather enough data points to average out daily fluctuations. So if you're reading this and even slightly curious — set up as many as possible ASAP! Storage is cheap, especially for single-user data like this, and in the worst case you can just delete it all. In the best case, you can know yourself a little better numerically!</p>
<h2 id="suggestions">Suggestions?</h2>
<p>I hope this gives you some idea of the scope of data that can be collected for minimal effort on your part, and the potential cool graphs you can make from it. If you think of any neat relationships I should look for or other data I could collect, please DM and tell me!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/allmydata/</guid>
      <pubDate>Fri, 26 Jan 2024 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Fitbit timestamps cannot be trusted</title>
      <link>https://andykong.org/blog/fitbittsproblem/</link>
      <description>Please recommend me alternatives</description>
      <content:encoded><![CDATA[<html><body><p>Yesterday I slept kinda funny and discovered something horrible about Fitbit's data collection.</p>
<h1 id="the-story">The Story</h1>
<p>On January 14th, I got tired early and slept from 8pm to midnight, then got up until I slept again from 5am-10am. In the afternoon, I noticed that my Fitbit's time was off — this happens if the battery is allowed to be dead for more than 1-2 days. It said 10:xx on January 13th (Sat), but the time was actually 4:xx on January 15th (Mon), meaning it was off by ~42 hours (or 54, not sure the am/pm). </p>
<p>When I synced my Fitbit app from the phone, it usually fixes the time shift, so I did that. Then I looked at my sleep history and saw it. </p>
<p><img alt="" class="addpic" src="/static/fitbittsproblem/0.png"/></p>
<p>Here we see my two sleep sessions logged under Friday — but the times are totally wrong, 2pm-6pm then 11pm-3am. </p>
<p>Consider that before I synced it, my Fitbit time was ~6 hours behind the real time (said 10, was 4). If we add 6 hours to the sleep logs, the times become correct (8pm-12am, 5am-9am). This is horrible!</p>
<h1 id="why-is-it-horrible">Why is it horrible?</h1>
<p>I always assumed that if the Fitbit's clock was wrong and sleep data is recorded, the data's timestamps would get back-corrected when syncing with a true time source (smartphone). Instead, Fitbit just keeps the old (wrong!) timestamps of the data and corrects the device time for future data collection.</p>
<p>The way Fitbit handles this data logging is even worse than just deleting it — instead of no data (which cannot screw up your analyses), I have a incorrectly-recorded night of sleep data with the wrong timestamps (which can screw up your analyses). </p>
<h1 id="maybe-its-not-that-often">Maybe it's not that often?</h1>
<p>The Fitbit dies after a week, I leave it uncharged for long enough about 1/5 of the time. I don't sync my Fitbit very consistently, meaning up to ~4 days of data can have the wrong timestamps. 4/35 is more than 10% of my data, recorded with incorrect timestamps, completely uncorrectable and undetectable when doing sleep analysis on myself. Whatever the effects, it sure doesn't help me.</p>
<h1 id="what-now">What now?</h1>
<p>I believed it was common sense to correct data timestamps that were recorded when the "true" time is unknown (e.g. waking up from dead battery), but it seems that collecting accurate data is not Fitbit's forte. First the thing about the <a href="../fitbitsleeptzcorrection">timezones</a>, now they're persisting incorrect timestamps — I'm officially in the market for a better fitness tracker. No Fitbit, all their trackers will share these same problems.</p>
<p>I would take the Apple Watch if it had less screen, and I'm considering Xiaomi but I haven't looked into if the data export is any better. Whoop looks good, but is a bit pricy for an unemployed boy like me. Oura also, but the ring is too chunky. Arghh, what happened to the market fulfilling my needs?</p>
<p>Anyway, please DM me if you know of an alternative tracker that DOESN'T ruin the data while collecting it. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/fitbittsproblem/</guid>
      <pubDate>Mon, 15 Jan 2024 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Bionic reading doesn't help you read faster</title>
      <link>https://andykong.org/blog/bionicreadingtest/</link>
      <description>An N=1 experiment</description>
      <content:encoded><![CDATA[<html><body><p>Hello!</p>
<p>A few posts ago, I read the Percy Jackson and the Olympians series again and timed how long I took to read each one. By reading 4 of them on my computer and 1 in physical form, I accidentally ran an experiment comparing my reading speed on screens vs. on paper (<a href="../printvsdigital">results here</a>).</p>
<p>Today I have some more results, this time regarding the nu-age speed-reading technique called "Bionic Reading". I have read the first two books of the next Olympians series by Rick Riordan, one with Bionic Reading and one without. </p>
<hr/>
<h1 id="inspiration">Inspiration</h1>
<p><img alt="" class="addpic" src="/static/printvsdigital/2.png"/></p>
<p>Looking at the graphs of my reading speed from the previous post, I find it amazing how close the reading speeds are for each book. While I think I read at a fairly consistent rate, Rick Riordan must also target a word count, and the vocab level must be held consistent across each one. To me, this ability to write series of books around the same character or universe with an audience that stays invested seems like a staple of young-adult / teenage fiction. Or maybe just for entertainment novels? I'm thinking of</p>
<ul>
<li>Warriors series (8 series of 6 books each)</li>
<li>Percy Jackson-ish stuff (3 series of 5 books about Greco-Roman pantheon, 3 books on Egyptian, 3 books on Norse)</li>
<li>Harry Potter (7 book main series)</li>
<li>Skulduggery Pleasant (currently on 3rd series, 16 books total)</li>
</ul>
<p>Anyway, this consistency is great for us. It means we can treat each book as approximately the same as any other one, then use them to compare different reading techniques!</p>
<h1 id="todays-intervention-bionic-reading">Today's intervention: Bionic Reading</h1>
<p><strong>Bio</strong>nic <strong>Rea</strong>ding <strong>is</strong> <strong>th</strong>is <strong>co</strong>ol <strong>hi</strong>p <strong>ne</strong>w <strong>tech</strong>nique <strong>whe</strong>re <strong>th</strong>e <strong>fi</strong>rst <strong>ha</strong>lf <strong>of</strong> <strong>ea</strong>ch <strong>wo</strong>rd <strong>i</strong>n <strong>a</strong> <strong>te</strong>xt is bolded. Since we read text in saccades, the inventor(s) believe that bolding the first, anchor part of a word would help people read text faster by allowing the eye to lock onto the next word more quickly.</p>
<p class="caption"> Example of Bionic Reading text </p>
<p><img alt="Example of Bionic Reading text" class="addpic" src="/static/bionicreadingtest/0.png"/></p>
<p>As far as I can remember, this was marketed to me as a way of reading better suited for ADHD / neuro-diverse people, a sort of salesman's trick like <a href="https://en.wikipedia.org/wiki/Irlen_filters">Irlen filters</a> that supposedly speeds up reading rates in return for money (backed by science!). </p>
<p>But whatever, I love running experiments, especially tiny ones.</p>
<h1 id="experimental-setup">Experimental setup</h1>
<p>Rick Riordan's 2nd Greco-Roman fantasy series is also 5 books long, features characters from the first series, and has similar average length (~120k words/book vs. ~85k). The first book I read normally, as a book file converted to HTML. Here is what it looks like:</p>
<p><img alt="" class="addpic" src="/static/bionicreadingtest/1.png"/></p>
<p>The second book I read using a Chrome extension called <a href="https://chromewebstore.google.com/detail/jiffy-reader/lljedihjnnjjefafchaljkhbpfhfkdic">JiffyReader</a>, which bolds the first half of each word in the text, making it "Bionified". Here's what it looks like now:</p>
<p><img alt="" class="addpic" src="/static/bionicreadingtest/2.png"/></p>
<p>My process was a bit convoluted because the real Bionic Reader converter 1) costs money and 2) doesn't preserve the format, which definitely affects reading speed. </p>
<h1 id="results--discussion">Results &amp; Discussion</h1>
<p>Here are my reading speeds for the first two books. The left is read normally, the right is read with Bionic text. Yada yada small sample size, can't draw statistically whatever conclusions, etc.</p>
<p>Bionic Reading felt the same to me as normal reading after 10 pages, I did not feel less eye fatigue, nor did I read faster subjectively or objectively. I'm pretty sure this is a gimmick. </p>
<p><img alt="" class="addpic" src="/static/bionicreadingtest/3.png"/></p>
<hr/>
<h1 id="extras">Extras</h1>
<p>Also the website is awful (I'm not strawmanning, it's unrelated), check it out:</p>
<p><img alt="" class="addpic" src="/static/bionicreadingtest/4.png"/></p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/bionicreadingtest/</guid>
      <pubDate>Thu, 11 Jan 2024 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>How sleep deprivation affects my cognition</title>
      <link>https://andykong.org/blog/stroopvssleep/</link>
      <description>Correlating my Stroop with sleep data</description>
      <content:encoded><![CDATA[<html><body><p>Hello! I've been collecting my <a href="../../projects/strooper">Stroop effect data</a> for about three months now, and I also have my Fitbit collecting sleep data from the whole period. A friend asked me how sleep affects my Stroop scores, and I went ahead and set up the analysis.</p>
<p class="caption"> Stroop effect — quick, name the colors of the words </p>
<p><img alt="Stroop effect — quick, name the colors of the words" class="addpic" src="/static/strooper/img_1.png"/></p>
<h1 id="methods-and-results">Methods and Results</h1>
<p>Here is a lot of scatterplots where my three Stroop scores (please see above link for more info) are arranged according to some sleep variables (total time in bed, sleep efficiency, time asleep, time awake), and then I do a linear regression for each one.</p>
<p><img alt="" class="addpic" src="/static/stroopvssleep/top.png"/></p>
<p><img alt="" class="addpic" src="/static/stroopvssleep/bottom.png"/></p>
<p>I'm not just gonna throw a best-fit line on there and call it a day — I want to know how real that line is. I've also calculated a p-value which is the probability that the best-fit line slope is that value by random chance. I also <a href="https://en.wikipedia.org/wiki/Bonferroni_correction">Bonferroni-corrected</a> the threshold value, though I think this boundary is a bit arbitrary (we pick p=5% arbitarily like everyone else, then divide by 12 to Bonferroni correct. p&lt;.00416 is significant).</p>
<h1 id="discussion">Discussion</h1>
<p>As you can see, a few variables appear to be related (most notably Stroop score vs. sleep duration), but aren't actually by our experiment standards. I would need more data to confirm, and I only have 27 days of data because Stroop data is relatively new in my stack of tracked personal info. </p>
<p>If the top-right graph were significant, then we could say that my Stroop interference time goes down with a longer sleep duration. We expect smarter people to have a lower Stroop interference, so if we believe more sleep good then my data shows the expected relationship. However, I've been pondering <a href="https://guzey.com/theses-on-sleep/">Guzey</a>'s work lately re:sleep deprivation is good, and I hoped to see something surprising. Alas, we will wait for me to generate more data. </p>
<hr/>
<h1 id="aside-or-favor">Aside or Favor</h1>
<p>I need people like you, dear reader, to help me with a project.</p>
<p>If Quantified Self is your thing, I'm working on a dashboard with the ability to upload arbitrary time series data and see how it correlates with your other data. There's other dashboards, but they only accept API-integrated data — our project will be for the <a href="https://www.businessinsider.com/bryan-johnson-sleeps-with-small-device-on-penis-track-erections-2023-9">people who track their nightly erection duration</a> in an Excel spreadsheet and want to see how their water intake changes the next day.</p>
<p>I've written a bit of framework code for this, OOPy stuff — the code is generalizable enough to work across diverse data, but it lacks polish. If you're interested in using what I've written to analyze your data (code request) or are interested in working on this project and have the time to commit to it (collaboration), please email me! I am trying to bring this dash to fruition for others to use. In particular, please contact me if you know anything about modern software best practices, because I do not. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/stroopvssleep/</guid>
      <pubDate>Wed, 10 Jan 2024 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Reading speed comparison on paper vs screens</title>
      <link>https://andykong.org/blog/printvsdigital/</link>
      <description>Also reflections on the Percy Jackson series</description>
      <content:encoded><![CDATA[<html><body><p>I recently re-read the first five books of the Percy Jackson series. I'll share some data on reading speed, as well as my thoughts on the series after re-reading as an adult.</p>
<p><img alt="" class="addpic" src="/static/printvsdigital/0.jpg"/></p>
<hr/>
<h1 id="data">Data</h1>
<p>I read most of the PJ series on my computer, and use Timing to log the time spent reading each one. However, I happened to find my old physical copy of the third book on my shelf, and read that one on paper — since I used my computer right before and after reading it, we also have the time spent on that one. I'm getting word counts of each book from <a href="https://www.reddit.com/r/camphalfblood/comments/hm3gh8/numbers_the_length_of_percy_jackson/"> this reddit post </a>.</p>
<p><img alt="" class="addpic" src="/static/printvsdigital/1.png"/></p>
<p>It took me 503 minutes to read all of them:</p>
<p><img alt="" class="addpic" src="/static/printvsdigital/2.png"/></p>
<p>Now, the difference is not huge, but the only book where I read the physical copy was also the slowest read. Weird! Originally the difference was bigger because I overzealously counted how long it took me to finish book 3, but it is slow even after correction. This made me think about potential reasons — extra sleepy while reading, bad lighting, etc.</p>
<p>Then I thought that the page turning time was adding to the read time. Since I always scroll extra far down, I never have to wait to turn a page while reading on my computer.</p>
<p>To test this, I timed how long it took to turn 10 pages and read the first word on each page. For physical books, this took 15.8 seconds, and on my books app, it takes 7.32 seconds. Book 3 is 300 pages, so I can estimate wasted page turning time at ~254 seconds, which is not large enough to have affected the above graph (only 0.5% of the total read time). Still, interesting. </p>
<hr/>
<h1 id="thoughts">Thoughts</h1>
<p>I remember receiving the first three books in a box set from a family friend, and I still recall the general plot and twists. Percy Jackson is comfort reading to me since I've read them before, and since they're targeted at young teenagers, the language and speech mannerisms are simpler than some adult-er books I've read. Sentences were often just simple ones for paragraphs on end. </p>
<p>Something I found annoying about the books this time is the constant topic switches — two characters get alone, they're about to exchange important info or a secret, and then a third character always butts in with an urgent message or incoming threat. The main character consistently mentions his ADHD, which seems to tie in with the pacing of events in his story — maybe this book is targeted at children with ADHD as a way of making them feel seen?</p>
<p>Another element in the books that may be a reflection of the teenage audience is the cagey romantic bits — they dance around the main character's romance without resolution for the entirety of five books, probably matching pace with the romantic expectations of much of the audience. I guess it would be hard for teens to empathize with a book character's emotional life if they were far more advanced than their own experience. </p>
<p>I felt a similar annoyance at the Fantastic Beasts movies — the two main characters are always interrupted before they can really talk to each other, and the audience is let in on a bit of information that isn't really shared. I forget the word for this concept, when the audience knows something that the characters don't, but the movies really abuse it. I think Fantastic Beasts relies on the audience's desire to see the romance blossom in order to get them to come back and watch the next one. Alas, the romance here has not advanced after 3 movies, and I resent the usage of this trick.</p>
<p>That's all, cya next time!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/printvsdigital/</guid>
      <pubDate>Tue, 09 Jan 2024 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Shipping from Europe to America from $2.30/kg</title>
      <link>https://andykong.org/blog/packingtips/</link>
      <description>How to game cross-continental travel</description>
      <content:encoded><![CDATA[<html><body><p>Hello! After my undergrad, I moved from the US to Europe, taking all my earthly possessions. A year later, I have moved back to the US, taking, again, all my earthly possessions. I am extremely susceptible to the <a href="https://en.wikipedia.org/wiki/Endowment_effect">endowment effect</a> and I'm not rich yet, so I put in a lot of effort and moved back nearly everything I took there and then some without paying much extra — the process took quite a bit of repacking and rethinking, so I'd like to share my insights with you, dear reader. </p>
<p class="caption"> Interesting endowment effect factoid, usually people use the analogy that a mug you own is worth much more to you than the value of the mug. I did not manage to take my ThorLabs mug back to America with me :( </p>
<p><img alt="Interesting endowment effect factoid, usually people use the analogy that a mug you own is worth much more to you than the value of the mug. I did not manage to take my ThorLabs mug back to America with me :(" class="addpic" src="/static/packingtips/0.png"/></p>
<p>This will be mostly targeted at people who carry around a lot of extraneous stuff related to a hobby or maybe a medical condition. I do a bit of electronics and the components and heavy equipment comprise approximately 30kg of my possessions. For instance, these two machines (oscilloscope, function generator + power supply) constitute a basic electronics lab totalling ~15kg, which I brought back from Europe. </p>
<p><img alt="" class="addpic" src="/static/packingtips/1.jpg"/></p>
<h1 id="packing">Packing</h1>
<p>Before you start, it helps to mentally sort your stuff by density. For example, clothes are low density, books wood and weirdly shaped knick-knacks are medium, and metal/stone/ceramic stuff is high density. I also knew I would have two checked bags (23kg x 2) and a carry on (18kg in non-American countries) with personal item (no limit), with the total weight &gt;64kg.</p>
<p>While you may think it makes sense to pack densely in the checked bags so you won't have to lug it around, you actually need to save the densest stuff for the carry-on and personal item. This is because they always check the weight of your checked bags but never your carry-on, so it doesn't matter if the carry-on is super heavy. Also, checked bags are bigger, and oftentimes you will have to pack the biggest, fluffiest stuff in there because they do not fit anywhere else.</p>
<p class="caption"> Burgeoning is a good word for these bags </p>
<p><img alt="Burgeoning is a good word for these bags" class="addpic" src="/static/packingtips/2.png"/></p>
<p>Not all the clothes go into the checked bags. Anything potentially breakable should be padded with clothes, preferably fluffy items like sweaters or puffers. Think of the luggage like a sushi roll — the valuables are inside, guarded by many layers of cushy clothing. Valuables should also be insulated from knocking into each other within the luggage center.</p>
<p>If you're really tight on space, you'll have to do a bit more optimization, sorting the clothes by density to determine which luggage is padded with what. I packed all the low-medium density stuff in my large bags, padded with the lowest density clothing so it wouldn't go overweight. Higher density clothes like pants and shoes went into the carry-on. This means the carry-on is less protected, but you're not going to throw it around if you're handling it yourself, right?</p>
<p class="caption"> Folded clothes DO NOT GET SMALLER </p>
<p><img alt="Folded clothes DO NOT GET SMALLER" class="addpic" src="/static/packingtips/3.png"/></p>
<p>When you pack clothes, ditch the rolling and the folding routine. Just think about it — clothes only waste space when they're folded. Flat fabric sheets waste zero volume, but you add empty volume in the crease once you fold it. Fold your clothes as little as possible. Ideally they go into a suitcase that is exactly their shape and lie totally flat, but this is an imperfect world.</p>
<h1 id="at-the-airport">At the airport</h1>
<p>Arrive early, your stuff is heavy and you'll move slower accordingly. Oh, and you will get stopped at the security checkpoint — they claimed they could not see through my bag and had to unpack everything to re-scan it, then I had to pack everything again at that little side counter where they make the suspicious people hang out.</p>
<p class="caption"> A couple friends going to the airport </p>
<p><img alt="A couple friends going to the airport" class="addpic" src="/static/packingtips/4.jpg"/></p>
<p>You should also know your bag weight just so there's no head-scratching repacking in the check-in line. Try to bring a friend. A couple came with me to the airport, bringing a small sack in case I needed to shed some cruft. Luckily, both my bags werighed in at 22.8kg/23kg. The staff are usually a bit lenient to overweight bags by about +0.5kg, but I didn't want to rely on the kindness of the Swiss staff.</p>
<p>After checking my two big luggages, I had only my burgeoning backpack and carry-on. Usually nobody cares how big/heavy they are, but once I flew Swiss and a person actually stopped me after the check-in because they said my carry-on bag "looked heavy" — it was, and cost me 60 bucks to check it separately. Avoid people who look like they're patrolling. You must be careful in this crazy world.</p>
<h1 id="getting-rid-of-your-carryon">Getting rid of your carry-on</h1>
<p>Something that never made sense to me is at the check-in, you can pay money to check your bag, but once you're in the airport and they've filled all their overhead storage, all of a sudden it's free! We can exploit this little glitch to ditch our large suitcase.</p>
<p>At your gate when they start calling out group numbers to start boarding, just don't move. You just keep reading/crocheting/twiddling your thumbs, whatever you were doing before. You have assigned seats anyway.</p>
<p>When the line of fellow passengers diminishes to nobody, go check in. We've now saved ~30 minutes of just standing in line, and usually those early riders will have used up all the storage. If this is the case. the staff will take one look at your hulking, totally-regulation carry-on and check it for free. Voila, now you just have to get you and your personal item to your destination — all your earthly possessions will follow along your whole journey without you needing to worry about a thing.</p>
<h1 id="peace-of-mind">Peace of mind</h1>
<p>Ah, you don't trust the airport staff with all your earthly possessions? Yea, they do have a <a href="https://thepointsguy.com/news/lost-baggage-report-2022/">tendency to misplace luggage ~1% of the time</a>. I always keep an Airtag in my checked luggage just to make sure it's following along — also, if they lose it, we'll have the small comfort of knowing where it ended up.</p>
<h1 id="total-cost">Total cost</h1>
<p>My ticket had an included checked bag (75$), and I paid 100$ for the 2nd one. My two checked bags weighed 22.8kg each, carry-on weighed 20kg, and my backpack weighed ~10kg. 175$/75.6kg comes out to 2.31$/kg, beating out every shipping option I could find online. For reference, UPS charges approximately 9$/kg to ship a package (I'm using the weight of both my checked bags, since the carry-on is free anyway).</p>
<p><img alt="" class="addpic" src="/static/packingtips/5.png"/></p>
<h1 id="planned-obsolescence">Planned obsolescence</h1>
<p>This blog post will be rendered unnecessary once an hour of my life spent repacking is worth more money than 9$/kg. I'll still think applying optimization to the feat of packing is an enduring skill though, which will serve me greatly in my other arenas of life. Also, fuck rolling your clothes.</p>
<h1 id="conclusion">Conclusion</h1>
<p>If you don't want to <a href="https://vitalik.eth.limo/general/2022/06/20/backpack.html">remodel your whole life around travelling</a>, keeping these lessons in mind is a nice compromise. Hope you enjoyed reading, and that my painful insights become your pro-tips.</p>
<p>Cya around!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/packingtips/</guid>
      <pubDate>Mon, 01 Jan 2024 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Shot comparisons between an iPhone and digital camera</title>
      <link>https://andykong.org/blog/phonecamvsrealcam/</link>
      <description>Sensor size matters!</description>
      <content:encoded><![CDATA[<html><body><p>Recently Frido got a nice camera and took some photos of me. They looked great, with nice color and incredible close-up detail. However, when I imported them into my photos library, I saw that the shots were only in 2K (1920x1080). My phone camera consistently shoots photos in 4K, but doesn't look nearly as good. What gives?</p>
<p>We talked about it on a tram and took some side-by-side shots, him with his camera and me with my phone. Even from his camera's tiny LCD screen, the closeup detail in the camera shot was incredibly clear compared to mine, which looked dusty and blurry. Unfortunately we have lost those test photos, but luckily I borrowed a similar camera to take comparison shots with.</p>
<p>Here are two photographs of the same scene. The first photo is taken with the Canon EOS 3 (APS-C sensor), while the horizontal one is taken with an iPhone 13.</p>
<p class="caption"> Canon shot </p>
<p><img alt="Canon shot" class="addpic" src="/static/phonecamvsrealcam/0.jpg"/></p>
<p class="caption"> iPhone shot </p>
<p><img alt="iPhone shot" class="addpic" src="/static/phonecamvsrealcam/1.jpg"/></p>
<p>I have some closeup shots, but the comparison is kinda impaired by the lower resolution of the Canon. Again, the Canon is the first shot. </p>
<p class="caption"> Curtain closeup, Canon shot </p>
<p><img alt="Curtain closeup, Canon shot" class="addpic" src="/static/phonecamvsrealcam/2.jpg"/></p>
<p class="caption"> Curtain closeup, iPhone shot </p>
<p><img alt="Curtain closeup, iPhone shot" class="addpic" src="/static/phonecamvsrealcam/3.jpg"/></p>
<p>The Canon's details on the right side of the curtain come out better, but the iPhone does a bit better on the left side (less lighting). Even though more detail is resolvable in the iPhone photo, the whole shot looks a bit washed out - the colors are much crisper with the Canon.</p>
<p>On the left wall, the iPhone image is much grainier, and I think the weird coloration on the Canon shot is from the JPG encoding and not the camera itself. </p>
<h1 id="does-it-come-from-sensor-size">Does it come from sensor size?</h1>
<p>The Canon APS-C sensor size is 25.1×16.7 mm (or 419.17 $mm^2$) while the iPhone 13's is 44 $mm^2$ total, almost a 10x increase. This larger sensor should let the camera capture more light for each "photographable region", reducing any graininess in flat areas and offering better capture for lower-light regions. This is how Frido explained the difference in the two cameras to me. </p>
<p>But I'm not sure. The iPhone benefits from the many millions Apple poured into computer vision research for ML image enhancements, and doesn't do too badly against the Canon. Take a few pics and average them out, maybe the grain will go away and the resolution is still ~2x higher than the Canon camera? </p>
<h1 id="what-about-video">What about video?</h1>
<p>I'm interested in shooting video with a slightly nicer camera than my iPhone, so this is the primary focus of my curiosity. I tried to take a comparison video on the camera as well, but it shoots video in a lower resolution than it takes photos.</p>
<p>So the camera is not ideal for taking photos with, but then how much does sensor size matter for video cameras? I could believe that the optimizations are way different if we're encoding using H.264 instead of just capturing photographs every once in a while.</p>
<h1 id="conclusion">Conclusion</h1>
<p>Anyway, I'm sure this is one of those naive blog posts I write and then regret once I learn a bit more about cameras, but currently it's still news to me.</p>
<p>Cya next time!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/phonecamvsrealcam/</guid>
      <pubDate>Sat, 23 Dec 2023 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Teardown of the Freestyle Libre 2 Continuous Glucose Monitor</title>
      <link>https://andykong.org/blog/teardownlibre2cgm/</link>
      <description>Pictures and wild speculation</description>
      <content:encoded><![CDATA[<html><body><p>Hello! Recently I wore a Freestyle Libre 2 continuous glucose monitor for two weeks. They're sold over-the-counter in France for about 40 Euros a pop, and I was curious about what my glucose data could tell me about my other bodily functions.</p>
<p><img alt="" class="addpic" src="/static/teardownlibre2cgm/0.jpg"/></p>
<p>Here's the open box. The monitor + injector comes in two pieces. The bottom sealed cup has the monitor in it, here you can see the little triangular piece that has the needle.</p>
<p><img alt="" class="addpic" src="/static/teardownlibre2cgm/1.jpg"/></p>
<p>You click the top grey part into the bottom cup and the monitor gets loaded into the applicator, needle-side out. </p>
<p><img alt="" class="addpic" src="/static/teardownlibre2cgm/2.jpg"/></p>
<p>This gets injected into your arm or hip somewhere, where it connects to an app via Bluetooth.</p>
<p>After about two weeks, my app said the monitor's two week lifespan had ended, and that I should purchase another one. Since I didn't notice any noticeable correlation between my blood glucose peaks and bodily functions (brain fog, tiredness, energy, etc.), I didn't opt to get another one.</p>
<p>I kept the sensor around because I wanted to know how it worked, and finally got around to deconstructing it
<br/></p><hr/>
<h1 id="teardown">Teardown</h1>
<p><img alt="" class="addpic" src="/static/teardownlibre2cgm/3.jpg"/></p>
<p>First I removed the needle assembly, it has these three black squishy contacts that the needle feeds into. These touch the three golden circular pads on the Libre Freestyle 2</p>
<p><img alt="" class="addpic" src="/static/teardownlibre2cgm/4.jpg"/></p>
<p>From the top, it looks like the two shells are held together by a plastic rod on one going into a plastic hole on the other, and the fit is quite snug. I started using pliers to tear into the plastic housing and that worked nicely.</p>
<p><img alt="" class="addpic" src="/static/teardownlibre2cgm/5.jpg"/></p>
<p>One of the first things I encountered was this taped sensor. I didn't have my multimeter on me to check, but it looks a bit like a thermal resistor used to roughly estimate temperature changes. Maybe for skin detection?</p>
<p><img alt="" class="addpic" src="/static/teardownlibre2cgm/6.jpg"/></p>
<p>After removing all the plastic, we can take a nice gander. The main microcontroller has an RF430 marking, looks like the TI RF430 NFC chip which also has an ADC.</p>
<p><img alt="" class="addpic" src="/static/teardownlibre2cgm/7.jpg"/></p>
<p><img alt="" class="addpic" src="/static/teardownlibre2cgm/8.png"/></p>
<p>I heard that the older Freestyle Libres used NFC only, requiring a user to tap their phone to the device to get readings out periodically. But mine had bluetooth, so what's doing that? There's also a PCB antenna at the top for wireless communication, furthering my suspicions that NFC is not the only thing here...</p>
<p><img alt="" class="addpic" src="/static/teardownlibre2cgm/9.png"/></p>
<p>I'd venture a guess it's this chip on the the right, but I can't quite make out the letters in this picture. The antenna + clock right next to it seem to imply this IC does some thinking, and it may be responsible for the Bluetooth. </p>
<h2 id="guard-ring">Guard Ring</h2>
<p><img alt="" class="addpic" src="/static/teardownlibre2cgm/10.png"/></p>
<p>These crop circle-looking contacts for the needle are also interesting. I think this is the first time I've seen guard rings actually put to use in a circuit. They continue on the back</p>
<p><img alt="" class="addpic" src="/static/teardownlibre2cgm/11.png"/></p>
<p>I'm no expert, but I think guard rings are placed around the high input impedance inputs of an op-amp or ADC to prevent leakage current from affecting the measurement. Because the input signal can also have a high impedance, the leakage current across the PCB soldermask can significantly alter the signal. The guard ring is a grounded circle which attenuates any leakage current by shorting it out before it can get to the signal path. I'm guessing the signal that this CGM looks at is quite small or sensitive to noise.</p>
<p><img alt="" class="addpic" src="/static/teardownlibre2cgm/12.png"/></p>
<p>Here's another picture of the CGM against the light. You can see both the guard inputs and some of the inputs from the main NFC chip. There's also a big copper trace that loops the whole board a few times - this looks like the coil that the NFC chip uses.</p>
<p>I also wanted to measure the battery voltage at the end of the two weeks to see if the battery had really run out, or if this was some kind of planned obselescence play. Unfortunately I waited too long to check.</p>
<p>Also, I believed that the needle had some kind of special liquid which turned the glucose level into a voltage. This doesn't seem to be the case, at least I couldn't see anything that would imply some "glucose electrifying liquid" that gets used up. There is a weird little loop of wire though on the needle, not sure what that is...</p>
<p><img alt="" class="addpic" src="/static/teardownlibre2cgm/13.jpg"/></p>
<p>The little black dots here have tiny holes on the surface, and feel quite rubbery. I wonder if my blood actually traverses the tube and goes up to the PCB, or if some other mojo happens lower down in the needle? </p>
<p>Anyway, that's all for now. I will be posting the results I got from the glucose data at some later time. Cya!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/teardownlibre2cgm/</guid>
      <pubDate>Mon, 18 Dec 2023 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Timezone correcting Fitbit sleep data</title>
      <link>https://andykong.org/blog/fitbitsleeptzcorrection/</link>
      <description>How to get your sleep data in UTC so you can actually do anything with it</description>
      <content:encoded><![CDATA[<html><body><p>Hello, dear Reader. </p>
<p>You’re probably here because you exported your Fitbit data so you could make some cool visualizations, and then realized that all the nights of sleep are recorded in device-local timestamps instead of UTC/GMT. I’m here to show you a way to convert most of that data to UTC so you can analyze it properly. </p>
<h2 id="how-do-we-know-that-fitbit-sleep-data-is-not-timezone-corrected">How do we know that Fitbit sleep data is not timezone corrected?</h2>
<p>On the forums, someone claimed that Fitbit data exports in <a href="https://community.fitbit.com/t5/Web-API-Development/Clarification-on-time-zones-and-date-time-values/td-p/925255">UTC timestamps</a> . I can show this is not true. One way this is really visible is in the plot of my bedtimes recorded on my Fitbit — even though I've lived in Europe, Asia, and the US throughout this period, the actual hour which I go to sleep is always the same. If the bedtimes were UTC-adjusted, they would have distinct vertical shifts whenever I drastically move timezones. </p>
<p class="caption"> My bedtimes, according to my Fitbit data </p>
<p><img alt="My bedtimes, according to my Fitbit data" class="addpic" src="/static/fitbitsleeptzcorrection/0.png"/></p>
<p>Here's another example. My sleep data from July 2021 have startTimes around 1-2am, and I was in Pittsburgh (EST, UTC-4) at the time. If the startTime were in UTC, I would have been sleeping around 10pm regularly, and I know for a fact that wasn't the case.</p>
<p class="caption"> Raw sleep data in table form. The timestamps don't line up with UTC given my location at the time </p>
<p><img alt="Raw sleep data in table form. The timestamps don't line up with UTC given my location at the time" class="addpic" src="/static/fitbitsleeptzcorrection/1.png"/></p>
<h2 id="how-do-we-fix-it">How do we fix it?</h2>
<p>As I browsed through my agglomerated Fitbit data (details on how to do that <a href="https://medium.com/@kongmunist/how-to-import-and-organize-your-fitbit-data-caeeff8c51dd">here</a>), I could only find sleep data that either shared local timestamps (useless), or were recorded 24/7 (impossible to sync w/ sleep sessions). That was until I looked at the <em>Sleep Stress Score</em> file. </p>
<p class="caption"> Sleep stress data, aka our saving grace </p>
<p><img alt="Sleep stress data, aka our saving grace" class="addpic" src="/static/fitbitsleeptzcorrection/2.png"/></p>
<p>It looks like the STRESS_SCORE column is usually finalized right after waking up, meaning the UPDATED_AT column usually lists the time 1-30 minutes after a sleep session ends. But unlike the raw sleep data, the stress data uses UTC timestamps!! </p>
<p>Looking at July 13th, endTime=8:36 (local, UTC-4), and UPDATED_AT for the 13th is 12:37 (UTC). For the 14th, 8:03 and 12:25.</p>
<p>I looked at a few other random ranges as well, and the shift looked consistent. Bingo!</p>
<p class="caption"> A few consecutive  </p>
<p><img alt="A few consecutive " class="addpic" src="/static/fitbitsleeptzcorrection/3.png"/></p>
<p>We just need to take each day of data in the stress scores and figure out which row of the sleep data it corresponds with. Then, we can timezone-adjust that row of sleep data, giving us UTC timestamped data that we can work with. </p>
<h2 id="refining-the-technique">Refining the technique.</h2>
<p>We naively match every stress timestamp with a sleep row, then subtract the stress timestamp from the endTime. This should give us the timezone offset of that sleep row, right? Nah!</p>
<p class="caption"> Chart of wildly varying timezone offsets from subtracting stress UPDATED_AT and endTime </p>
<p><img alt="Chart of wildly varying timezone offsets from subtracting stress UPDATED_AT and endTime" class="addpic" src="/static/fitbitsleeptzcorrection/4.png"/></p>
<p>A ton of the offsets are higher than +12, which aren't valid timezones! </p>
<p>Since we are using the UPDATED_AT column, the stress score update can happen at any point after the sleep time ends. In my data alone, it can change up to a week later! If we use this as the UTC version of the endTime, we will end up with incorrect timezone offsets. This jankiness results from our unintended use of the stress data. </p>
<h2 id="naive-filter">Naive filter</h2>
<p>If we don't mind throwing out some bad data, an easy first filter is to throw out all data with offsets higher than 12. This gives us only the reasonable sleep rows</p>
<p class="caption"> Timezone offset over the years, without the offsets over 12 </p>
<p><img alt="Timezone offset over the years, without the offsets over 12" class="addpic" src="/static/fitbitsleeptzcorrection/5.png"/></p>
<p>This is much cleaner, and you can clearly see the horizontal lines from when I lived in the same place for a while. But because we've removed some data, it's going to be hard to interpolate between points to fix the remaining noisy points — if a +1 shift lasts only a day, I'm not sure if I can just flatten it to its neighbors since I'm not sure its neighbors are actually just 1 day apart. And most offsets &gt; 12 happened during a day of travel, so a spurious point is likely not equal to its neighbors and many will be thrown out.</p>
<h2 id="less-naive-filter">Less naive filter</h2>
<p>Instead of tossing out offsets over 12, I tried to keep as many points as I could from the beginning. Starting with full data, every single-day "spike" in time offset was set to its neighboring values (e.g. [4,5,4,4] -&gt; [4,4,4,4])</p>
<p class="caption"> Same chart as before but removing spikes only. The title is lying </p>
<p><img alt="Same chart as before but removing spikes only. The title is lying" class="addpic" src="/static/fitbitsleeptzcorrection/6.png"/></p>
<p>Then, I kept only the sleep rows that had a neighboring point that agreed. My reasoning was that once the sleep timezones become stable, timezone offsets can be corroborated with adjacent points.</p>
<p class="caption"> 2nd stage of filtering, removing spikes then only taking points that have an adjacent, equal point </p>
<p><img alt="2nd stage of filtering, removing spikes then only taking points that have an adjacent, equal point" class="addpic" src="/static/fitbitsleeptzcorrection/7.png"/></p>
<p>This operation does throw out some data points, but it's the end of my filtering process. We are left with roughly ~82% of the sleep rows, and fairly reliable knowledge of the row's timezone offset. </p>
<h1 id="message-for-fitbit-devs">Message for Fitbit devs</h1>
<p>Using only local timestamps for sleep data makes it incredibly hard to do proper analysis between your sleep data and other, real scientific data sources recorded using Unix timestamps, like <a href="https://kongmunist.medium.com/playing-faster-tetris-by-sleeping-less-3d9b04d30349">my Tetris scores</a>. This is a huge gripe of mine regarding Fitbit, I mean, how hard can it be to add a UTC-corrected column to the sleep .csv? </p>
<h1 id="conclusion">Conclusion</h1>
<p>The code for the fitbit sleep data timezone correction can be found here: <a href="https://gist.github.com/kongmunist/a2ab8e7160ce9d540885b1fded08d13d">https://gist.github.com/kongmunist/a2ab8e7160ce9d540885b1fded08d13d</a></p>
<p>Good luck, it is a pretty straightforward script but I did not tidy it up at all. Cya next time!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/fitbitsleeptzcorrection/</guid>
      <pubDate>Thu, 30 Nov 2023 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Breakdown of my most used Alfred Workflows</title>
      <link>https://andykong.org/blog/alfredworth/</link>
      <description>Is Alfred worth buying?</description>
      <content:encoded><![CDATA[<html><body><p>On September 26, 2023, I spent 34 British pounds on a software license for Alfred 5, giving me access to some extra features like Workflows and Remote execution. This post is going to tell you about the Workflows that I've found, written, or co-opted to be useful to me.</p>
<p><img alt="" class="addpic" src="/static/alfredworth/0.png"/></p>
<hr/>
<p>Over time, I realized Alfred just makes a lot of little things easier, and we do a lot of those little things pretty often. Here's my Workflow list:</p>
<p><img alt="" class="addpic" src="/static/alfredworth/1.png"/></p>
<h2 id="1-quit-arena-or-all-but-messages-and-opera">1. Quit Arena, or "All but messages and Opera"</h2>
<p>This workflow is derived from <a href="https://github.com/vitorgalvao/alfred-workflows/tree/master/QuitArena">this other workflow called QuitArena</a>, and just closes all my apps except a preset few (Chrome, Messages, Opera, Spotify). It's nice for clearing my screen before a presentation, or just to context-switch to working on another project. Used about 2x per month</p>
<p><img alt="" class="addpic" src="/static/alfredworth/2.png"/></p>
<h2 id="2-change-audio-to-airpods">2. Change Audio To Airpods</h2>
<p>Runs two CLI scripts, one that Bluetooth connects to my Airpods (via their hard-coded MAC address) and another that switches the sound output to my Airpods. I use this about once a day, since they tend to connect to my phone first.</p>
<p><img alt="" class="addpic" src="/static/alfredworth/3.png"/></p>
<h2 id="3-clippaster">3. <a href="https://github.com/kongmunist/alfred_clippaster_workflow">ClipPaster</a></h2>
<p>ClipPaster pastes the last $n$ screenshots from my clipboard history to whatever application is focused. It's nice for copying relevant parts of several screens or pictures at once and then putting them all into a doc at once. I use it about once a week. Modified from ClipSaver by luckman212 (next one!)</p>
<p><img alt="" class="addpic" src="/static/alfredworth/4.png"/></p>
<h2 id="4-clipsaver">4. <a href="https://github.com/luckman212/alfred_clipsaver_workflow">ClipSaver</a></h2>
<p>ClipSaver does the same as above, but saves them to desktop instead of pasting them. I use it slightly less than once per week.</p>
<p><img alt="" class="addpic" src="/static/alfredworth/5.png"/></p>
<h2 id="5-email-myself">5. Email Myself</h2>
<p>When I write "m mdmaowaodijwaojdwoaij", this workflow shoots off my "mdmaowaodijwaojdwoai" message to my mailbox. Good for quick reminders, but I don't use it that often.</p>
<p><img alt="" class="addpic" src="/static/alfredworth/6.png"/></p>
<h2 id="6-launch-apps">6. Launch Apps</h2>
<p>While it's kind of silly or trivial, I find it way easier to bind hotkeys using Alfred than using the Settings-&gt;Keyboard-&gt;Keyboard Shortcuts method that's built-in to the Mac. This workflow surfaces some of my most-used applications. I use it a few times a day.</p>
<p><img alt="" class="addpic" src="/static/alfredworth/7.png"/></p>
<h2 id="7-system-settings">7. <a href="https://github.com/alfredapp/system-settings-workflow/">System Settings</a></h2>
<p>Surfaces the MacOS system settings menus and makes them searchable in Alfred. Really nice, I use it about once a day.</p>
<p><img alt="" class="addpic" src="/static/alfredworth/8.png"/></p>
<h2 id="8-translate-hotkey">8. translate hotkey</h2>
<p>I type "t something" and the "something" is immediately opened in Google Translate. 1-2x a day, but only because I live in a German-speaking area at the moment.</p>
<p><img alt="" class="addpic" src="/static/alfredworth/9.png"/></p>
<h2 id="9-url-handler">9. URL Handler</h2>
<p>I wrote this to handle arbitrary strings. YouTube links get downloaded as videos, Github links get downloaded as zips, other websites get turned into ".webarchive" pages. Pretty generically useful, I use it about once a week.</p>
<p><img alt="" class="addpic" src="/static/alfredworth/10.png"/></p>
<h1 id="worth-it">Worth it?</h1>
<p>I'm not going to do a dry, EA-style calculation of time saved, but I estimate Alfred saves me about 30s of annoying, flow-breaking tasks every day. And that number can only ever improve, so I think Alfred is a good purchase if you plan on using your computer a lot.</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/alfredworth/</guid>
      <pubDate>Sun, 26 Nov 2023 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Average clip duration in MILF Manor</title>
      <link>https://andykong.org/blog/milfpy/</link>
      <description>Gauging the audience attention span from TLC dating shows</description>
      <content:encoded><![CDATA[<html><body><p>Whenever I'm watching unpretentious TV like sports or reality TV, I calculate the average clip length. I get my phone stopwatch out, and enter a lap whenever a cut happens. </p>
<p>Do this maybe 20 times, and you usually get some idea of the distribution. Usually the cuts happen so quickly and fluidly that you may notice yourself <em>missing</em> a few cuts, even though every pixel on the screen dramatically changes and it seems like something you'd notice. </p>
<p>Recently I watched Milf Manor, a "reality" dating show where older women seeking younger men are paired with younger men seeking older women on a fancy island. The twist is that every young man is the child of one of the older women. Hilarity ensues (?). </p>
<p class="caption"> The complicated feature I used for detecting jump cuts </p>
<p><img alt="The complicated feature I used for detecting jump cuts" class="addpic" src="/static/milf_cutfeature.jpg"/></p>
<p>Common decency aside, this show jump-cuts like nothing else. I did my stopwatch thing but didn't want to stop there — I really wanted to know all about the cut length distribution. I downloaded an episode and used the change in standard deviation of the absolute color difference of two adjacent frames. When this number crossed ~30, I called it a cut and recorded the frame number.</p>
<p>Then I calculated a few stats about it. Here they are:</p>
<p class="caption"> Avg clip length is 2.48 sec, with the median at 1.92 sec </p>
<p><img alt="Avg clip length is 2.48 sec, with the median at 1.92 sec" class="addpic" src="/static/milf_stats.png"/></p>
<p>While the average clip length is 2.5 seconds, the median is only 2 seconds. I also find it interesting that the average human blink rate is 1 blink per 4-5 seconds, which means we can see around two clips between blinks. Since the average blink takes ~0.3 seconds, we also won't miss much of a clip while blinking.</p>
<p class="caption"> Milf Manor clip duration histogram </p>
<p><img alt="Milf Manor clip duration histogram" class="addpic" src="/static/milf_cutdistro.jpg"/></p>
<h1 id="conclusion">Conclusion</h1>
<p>The difficulty of identifying a cut in a video surprised me, but it makes sense if you consider all the possible transitions (fade to black, fade to another clip, slo-mo transition to real-time, etc.). There is even a library in Python called <a href="https://pypi.org/project/scenedetect/">scenedetect</a> which identifies these for you. Next time I will definitely use that for cut detection instead of making my own feature. </p>
<p>I have completely satisfied my curiousity about Milf Manor at this time. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/milfpy/</guid>
      <pubDate>Sun, 11 Jun 2023 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>iCloud Cleanup</title>
      <link>https://andykong.org/blog/icloudconfusion/</link>
      <description>Script for highlighting large videos in iCloud and a storage discrepancy</description>
      <content:encoded><![CDATA[<html><body><p>Hello!</p>
<p>Recently I received an email from Apple letting me know that my iCloud storage was full. </p>
<p class="caption"> Kind letter from Apple requesting I purchase more storage </p>
<p><img alt="Kind letter from Apple requesting I purchase more storage" class="addpic" src="/static/ic_appleemail.jpg"/></p>
<p>Since the cost of 200GB vs. 1TB jumps more than threefold, I embarked on a great exploration of alternatives. Since photos made up the majority of my storage (~127GB), I figured I could just go into my Photos album and click Sort By File Size.</p>
<p>...</p>
<p>Except this is not a feature offered by Apple. It's not even a feature in the iCloud browser. I guess it's a bit too technical for Apple Photos, and unfortunately it would also make it too easy to avoid paying for more iCloud storage. </p>
<p>Whatever the reason, I still wanted to go through my photos by file size. Several apps exist for going through your photos and highlighting duplicates, or into your videos and showing you the file size. The only problem — photos that have been offloaded to iCloud do not show up in these apps, so they're not actually super useful. Also, would you really want random apps to scan through all your photos? </p>
<p class="caption"> Screenshot from a photo file size viewing app, with non-downloaded photos represented as 0B </p>
<p><img alt="Screenshot from a photo file size viewing app, with non-downloaded photos represented as 0B" class="addpic" src="/static/ic_photosbysize.png"/></p>
<h2 id="janky-js-solution">Janky JS solution</h2>
<p>Instead, I went to the iCloud website and thought about what was possible. I navigated to Photos-&gt;Media Types-&gt;Videos, and zoomed out as far as I could. </p>
<p class="caption"> Videos anonymized by slow internet loading </p>
<p><img alt="Videos anonymized by slow internet loading" class="addpic" src="/static/ic_icloudphotos.png"/></p>
<p>We can see that each video has an accompanying duration box — as long as it's an HTML element, we can use JS to search and filter them. I found that every runtime box has the class <code>video-text-badge</code>. From there is was a simple matter to find all of them in the page, sort by their duration, and highlight the ones that were past some threshold. Here's the code:</p>
<script src="https://gist.github.com/kongmunist/a598bcdd8c226c3a3159b1a918344977.js"></script>
<p>Because iCloud only loads the elements that are on the page, I've made this into a function that runs on a timer so new elements get highlighted as they get scrolled into. Here's what it looks like:</p>
<p class="caption"> Videos bigger than 20s are surrounded by a red box, making them much easy to pick out </p>
<p><img alt="Videos bigger than 20s are surrounded by a red box, making them much easy to pick out" class="addpic" src="/static/ic_icloudphotoshighlighted.png"/></p>
<p>To use it, just open the Javascript console (right click page -&gt; Inspect Element) and paste in the entire gist. Now you can easily select multiple big videos from iCloud and download them before deleting, moving them into longer-term storage: secret HDD under your mattress, other cloud storage, etc etc. </p>
<hr/>
<h1 id="the-mystery">The Mystery</h1>
<p>So, I used this script to remove all my iCloud videos &gt;30s. The interesting thing is, after I had removed all the "big videos" and downloaded them, it cleared ~55GB from my iCloud <em>despite only downloading 7GB of videos.</em> Herein lies the mystery.</p>
<p class="caption"> All downloaded videos take up 8GB of disk space </p>
<p><img alt="All downloaded videos take up 8GB of disk space" class="addpic" src="/static/ic_dl1info.png"/></p>
<p class="caption"> iCloud storage reduces from 199GB to 143GB after downloading 7GB of videos </p>
<p><img alt="iCloud storage reduces from 199GB to 143GB after downloading 7GB of videos" class="addpic" src="/static/ic_dl1afterdelete.png"/></p>
<p>Somehow, those 7GB of video took up way more space in the cloud than on my hard drive. Interesting...</p>
<h1 id="experiment-1">Experiment 1</h1>
<p>I wanted to test this further. First, I uploaded a 4K video with a lot of motion. This took up 281 MB. My storage looked like this after uploading it:</p>
<p class="caption"> 4K video uploaded, iCloud says 145.33 GB used </p>
<p><img alt="4K video uploaded, iCloud says 145.33 GB used" class="addpic" src="/static/ic_dl2storagebefore.png"/></p>
<p>Then I downloaded it and deleted it. The file was still 281 MB. Here is the storage afterwards:</p>
<p class="caption"> 4K video deleted, iCloud says 145.6 GB used </p>
<p><img alt="4K video deleted, iCloud says 145.6 GB used" class="addpic" src="/static/ic_dl2storageafter.png"/></p>
<p>Removing a 281MB video frees up ~270MB. This adds up, which is puzzling. What about the other, older videos? </p>
<h1 id="experiment-2">Experiment 2</h1>
<p>I thought that maybe older videos could have multiple copies saved in iCloud, so I searched through my videos to see if I could find a shorter one that takes up a lot of storage space. I found one with a lot of graphs, iCloud said it took up 128 MB.</p>
<p class="caption"> Older big video that takes up 128 MB </p>
<p><img alt="Older big video that takes up 128 MB" class="addpic" src="/static/ic_dl3icloudinfo.png"/></p>
<p>When I downloaded it, the file was only 47 MB!</p>
<p class="caption"> Downloaded video file is 47 MB </p>
<p><img alt="Downloaded video file is 47 MB" class="addpic" src="/static/ic_dl3download.png"/></p>
<p>And here is my iCloud storage before and after</p>
<p class="caption"> iCloud storage before deleting the old video, 145.29 GB used </p>
<p><img alt="iCloud storage before deleting the old video, 145.29 GB used" class="addpic" src="/static/ic_dl3storagebefore.png"/></p>
<p class="caption"> iCloud storage before deleting the old video, 145.12 GB used, reduction of 170MB </p>
<p><img alt="iCloud storage before deleting the old video, 145.12 GB used, reduction of 170MB" class="addpic" src="/static/ic_dl3storageafter.png"/></p>
<p>So iCloud says the video is 128MB, I download it and the video is actually 48MB, and my free storage increases by ~170MB when I deleted it. Interesting!</p>
<h1 id="conclusion">Conclusion</h1>
<p>It's weird that my storage freed up more than 7x the removed files size, and weirder still that old, big videos appear to have a much larger storage footprint in iCloud than in real life. </p>
<p>I am mildly interested in finding out why this happens, but I am not interested/bored enough to do it myself. If one of you fine people figure it out, please let me know by emailing me.</p>
<p>Anyway, I have freed up &gt;50GB to fill with more inane videos, and written a small script that allows me to do it again in the future. Hope this proves helpful to you, dear reader.</p>
<p>Cya later!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/icloudconfusion/</guid>
      <pubDate>Tue, 06 Jun 2023 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>How to stop noisy NeoPixels</title>
      <link>https://andykong.org/blog/noisyneopixels/</link>
      <description>Audible annoyance from WS2812B LEDs</description>
      <content:encoded><![CDATA[<html><body><p><em>When I was small, I visited my friend whose dad was an electrical engineer. I got to talking with him, and asked "Hey, what's the point of that thick bit on my cables?"</em></p>
<p class="caption"> Big chunky cylinder on a charging cable </p>
<p><img alt="Big chunky cylinder on a charging cable" class="addpic" src="/static/nn_ferritebead.jpg"/></p>
<p><em>He said "that's a ferrite bead, it reduces noise," and I nodded in understanding — that explained why my cables never made noise.</em></p>
<p><em>About a decade later, I learned about electrical noise.</em></p>
<hr/>
<p><br/></p>
<p>Hello!</p>
<p>Recently, I've been playing with the Adafruit Circuit Playground Express. This is an incredibly nifty SAMD21 microcontroller board which I have been using exclusively for its built-in ring of NeoPixels.</p>
<p>NeoPixels are like multi-colored RGB LEDs but smarter, and it's easy to control a whole strip of 'em from just a few digital pins. But like all LEDs, the brightness control is implemented via PWM — in this particular case, at a frequency of 400 kHz or so. And while it should be out of the human hearing range, <em>I can hear it!</em></p>
<h1 id="electroaudible-noise">Electro-audible noise</h1>
<p>As it turns out, electrical oscillation often creates audible noise. Most people are probably familiar with electronics or wall warts that start whining or <a href="https://product.tdk.com/system/files/contents/faq/capacitors-0031/singing_capacitors_piezoelectric_effect.pdf">singing</a> when plugged into the wall. Usually it comes from a high frequency physical oscillation caused by the electrical oscillation. </p>
<p>I'm quite sensitive to noise, and I noticed a slight buzzing coming from my Neopixels when setting the brightness higher than 5/255. Since I'm making a desktop doohickey, I wanted to learn more about the noise, particularly how it changes with brightness. </p>
<p>In a stroke of luck, the Circuit Playground board comes with a microphone! I wrote a small script to vary the brightness and recorded sound pressure levels (SPL) for a 0.5 second window at each level. My board has a small cover which probably amplified the measured noise. </p>
<p class="caption"> Graph of NeoPixel noise in dB as brightness increases </p>
<p><img alt="Graph of NeoPixel noise in dB as brightness increases" class="addpic" src="/static/nn_dbvslight.png"/></p>
<p>Each line is represented by the LED color that produced it. Since white is produced by keeping all three LEDs on, it makes sense for it to be higher than all the others. </p>
<p>Now I could target low and high brightness to cause the least amount of noise.</p>
<h1 id="future-work">Future Work</h1>
<p>One factor I forgot to test is the pitch of the whining at each brightness. Some brightness levels sound more annoying than other levels, even with a lower SPL. This is probably related to the nonlinearity of human hearing. </p>
<p>With my NeoPixels on, I recorded the audio spectra to see where the whining lay. It turns out to be much lower than the PWM frequency of the Neopixels, which means I do not understand the root causes of the noise very well. </p>
<p class="caption"> The three noisiest frequencies are all much lower than the PWM frequency of the NeoPixels. Vertical lines are the whining, and horizontal ones are me dropping my phone </p>
<p><img alt="The three noisiest frequencies are all much lower than the PWM frequency of the NeoPixels. Vertical lines are the whining, and horizontal ones are me dropping my phone" class="addpic" src="/static/nn_points.png"/></p>
<h1 id="conclusion">Conclusion</h1>
<p>The Circuit Playground mic also has an FFT function, and someday I will get around to recording the spectrum along with the SPL. For now, I have minimized the overall sound, and that is enough. </p>
<p>Cya later!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/noisyneopixels/</guid>
      <pubDate>Wed, 31 May 2023 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Calendar Memories</title>
      <link>https://andykong.org/blog/calendarmemories/</link>
      <description>Personalized holidays in my calendar</description>
      <content:encoded><![CDATA[<html><body><p>When I first began my life, time was nothing to me. I did not notice it passing, nor did I care. I lived in the world of events, and as stuff happened, I noticed the stuff without caring what time it happened. I was incredible at reading analog clocks when I was in preschool, but my parents worried about the time on my behalf, delivering me to soccer practice or school whenever they started. </p>
<p>As I got older, specific times took on meaning. Classes starting and ending times, lunchtime, and school letting out were all hard-coded in my head, and I would think about them every time I glanced at the time. Over time, it only got worse. In college, I started keeping a calendar to track my courses and their locations. If a clock is a machine that produces time, a calendar is a factory. It is the ultimate time-keeping tool, containing every minute of your life and your past, theoretically forever in both directions if its electronic. Isn't that incredible? </p>
<p>You may not think so, but try this. One day while working on <a href="../../projects/carnegiecalendar">Carnegie Calendar</a>, I realized that you could put in ANY date you wanted for an event. </p>
<p>I scheduled a meeting for April 9th, 2150 that I probably won't be attending. I scrolled on my calendar to make sure the event showed up, and it was there, sitting as innocently as any other calendar event. Then I navigated to my 150th birthday, and that event was there too. I probably won't make it to that either. </p>
<p><img alt="" class="addpic" src="/static/cm_150th.png"/></p>
<p>If that's not insane to you, just think about it for a bit. To me, it felt like knowing exactly where I will be buried and visiting the plot of land 50 years in advance.</p>
<hr/>
<h1 id="sorry-that-was-a-tangent">Sorry, that was a tangent</h1>
<p>I'm actually here to talk to you about a way to create your own holidays.</p>
<p>You know how there are these preset holidays in your calendar that you didn't add, like "Presidents' Day" or "Thanksgiving Day"? We celebrate these events on certain days because these days were instrumental to the country's development, and that's cool. </p>
<p>But to me, these events are kinda just taking up space — I didn't make them, and I might not celebrate them. What I find much more interesting to commemorate are the events that were instrumental to my life and my development. Stuff like "First time I got stitches" or "Failed midterm" that I can look back on and smile because they happened. </p>
<p><img alt="" class="addpic" src="/static/cm_firststiches.png"/></p>
<p>Now, whenever I feel like I've experienced something life-changing, I put it on my calendar and set it to repeat annually. I've started added the year too, just so I can gauge how long it's been. </p>
<h2 id="in-the-farfuture">In the far-future...</h2>
<p>I know that if I continue this, my calendar will eventually be filled with ghosts — past iterations of myself experiencing my own history. My routine weekly work meetings will clamor with "First stiches" for space on my screen. Birthdays of people I don't remember will populate the top bar. But these are the holidays I have chosen for myself, and to me, they are worth remembering.</p>
<p>Give it a go!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/calendarmemories/</guid>
      <pubDate>Sun, 09 Apr 2023 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Infoglobe Tutorial Pt. 3 — Software</title>
      <link>https://andykong.org/blog/infoglobetutorial3/</link>
      <description>Adding bits to our atoms</description>
      <content:encoded><![CDATA[<html><head><style>
    /* Limit height. Show scrollbars when exceeding height */
.gist .blob-wrapper.data {
   max-height:40vh;
   overflow:auto;
}
    </style>
</head><body><p>Ok, welcome back for our final installment of the infoglobe tutorial series. If you've not been following along, here's links to the hardware mods that you'll need to have done before this post will be useful to you — <a href="../infoglobetutorial1">part 1</a> and <a href="../infoglobetutorial2">part 2</a>.</p>
<p><img alt="" class="addpic" src="/static/igt1_hero.jpeg"/></p>
<p>If you've forgotten, here's the device we're hacking - the Olympia Infoglobe. I have described it too often, so we're gonna get right into the meat of this post. </p>
<hr/>
<h1 id="code">Code</h1>
<p>We're using the Wemos D1 mini ESP8266/ESP32 breakout board to control our globe, it looks like this.</p>
<p><img alt="" class="addpic" src="/static/igt3_wemos.png"/></p>
<p>Boot up your Arduino IDE and download the "IRremoteESP8266" library from the Tools-&gt;Manage Libraries-&gt; then search for IRremoteESP8266. This library should be supported for ESP32s as well. Hit install, and twiddle your thumbs for a bit as it installs.</p>
<p><img alt="" class="addpic" src="/static/igt3_irlib.png"/></p>
<p>Now, copy past <a href="https://gist.github.com/kongmunist/a8bdadbacda4bcb129cd183f2f0fffc5">this gist</a> into a new Arduino file and upload it to your ESP board. </p>
<script src="https://gist.github.com/kongmunist/a8bdadbacda4bcb129cd183f2f0fffc5.js"></script>
<p>This script is a demo showing the usage of all the functions we have to make controlling the Infoglobe easier. If you read through the <code>loop()</code> function, then you'll begin to understand the code and become able to extend it to your own beneficial/nefarious purposes.</p>
<p>If all went well and your ESP and Infoglobe are both powered up, you should see a "Hello World" message swirling around. You can change the default message on line 47, or you can open the Serial Monitor to upload a message to the Infoglobe immediately. </p>
<h2 id="example-use-case">Example use case</h2>
<p>I've personally written some code which lets the Infoglobe connect to my wifi and access <a href="https://aksuper7.pythonanywhere.com/">this website</a> I made for receiving notes from my friends. If you type a message and my globe is plugged in, I'll be able to see the message right on my Infoglobe. Pretty cool, right? </p>
<p class="caption"> Website to connect digital friends to my physical environment </p>
<p><img alt="Website to connect digital friends to my physical environment" class="addpic" src="/static/igt3_example1.png"/></p>
<p>I know of other people using their infoglobes as a weather/disaster reporting station, or a way to visualize Alexa messages. </p>
<h2 id="alternatives-to-esp">Alternatives to ESP</h2>
<p>It should be possible to substitute in any microcontroller which is supported by one of the IRremote libraries, since it should work as long as the function names are identical.</p>
<h2 id="more-code">More code?</h2>
<p>There are some cool folks working on a more sophisticated platform for interacting with the Infoglobe, but I don't currently have the link for that. Hopefully I can update this soon with the link.</p>
<h1 id="thats-all-folks">That's all folks!</h1>
<p>What will you do with your Infoglobe? I'd love to see it! Please feel free to email me if you get it working :)</p>
<p>Happy hacking!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/infoglobetutorial3/</guid>
      <pubDate>Sun, 09 Apr 2023 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Infoglobe Tutorial Pt. 2 — Hardware Integration</title>
      <link>https://andykong.org/blog/infoglobetutorial2/</link>
      <description>Letting go of externalized electronics</description>
      <content:encoded><![CDATA[<html><body><p>Hello! We're still hacking the Infoglobe. This post is about internalizing the external electronics we were using in the <a href="../infoglobetutorial1">first tutorial</a>, as well as powering our microcontroller using voltage from sources inside the infoglobe so we don't need two power lines. </p>
<hr/>
<p>As you may remember from <a href="../infoglobetutorial1">last time</a>, we wired up the data LED and ran the wires out of the infoglobe, where they were connected to an ESP8266 via breadboard. This is a great testing setup for the Infoglobe because we can fine-tune and measure the data we're sending to control the Infoglobe's display as we're figuring out the IR protocol. </p>
<p>From here, I'd recommend fellow Infoglobers to first write the software to control the Infoglobe, just in case there are any repairable hardware problems that can be detected early. Assuming you've done that or are using known-working code, we'll proceed with internalizing the electronics</p>
<p class="caption"> Our testing setup from tutorial 1 </p>
<p><img alt="Our testing setup from tutorial 1" class="addpic" src="/static/igt1_testingsetup.jpg"/></p>
<hr/>
<h1 id="tools">Tools</h1>
<p>You'll need the following</p>
<ul>
<li>1x Infoglobe, opened</li>
<li>ESP8266/ES32 or other small microcontroller</li>
<li>Soldering iron + solder<ul>
<li>Good air filter or fan</li>
<li>Dexterity</li>
</ul>
</li>
<li>Various passives<ul>
<li>Thin or flexible wire, preferably both</li>
<li>Power switch, small (optional)</li>
<li>Diode (optional)</li>
<li>Big capacitor (optional)</li>
</ul>
</li>
</ul>
<h1 id="steps">Steps</h1>
<ol>
<li>Prep the microcontroller and its new home (µcompartment)<ul>
<li>Prep compartment</li>
<li>Attach the flyback diode</li>
<li>Attach the filtering capacitor</li>
</ul>
</li>
<li>Run power lines to our microcontroller<ul>
<li>Find ~5V using a multimeter</li>
<li>Run wires to the µcompartment</li>
<li>(optional) Make an in-line power switch for the microcontroller</li>
</ul>
</li>
<li>Run data lines to our microcontroller<ul>
<li>Connect LED to ground with a resistor</li>
<li>Solder new wire to the LED's high pin</li>
<li>Run data line to µcompartment</li>
</ul>
</li>
<li>Wire up the ESP<ul>
<li>Melt a hole in the battery compartment</li>
<li>Feed wires through the hole</li>
</ul>
</li>
</ol>
<hr/>
<h1 id="step-1-prepping-the-microcontroller">Step 1: Prepping the microcontroller</h1>
<p>First we're gonna prepare the microcontroller for implanting. The main thing we're worried about is power — it sucks to have to plug two power cables in to use a consumer device, so we're hijacking the 5.6V line from within the Infoglobe itself. The only problem is that it's the motor power, so it's noisy and has kickback. Feel free to skip ahead if you know all this already. </p>
<p>The motor's source is not the best. Starting up the spinning rotor requires a few kicks sometimes, evidenced in these sharp voltage drops on the scope. And even after the motor begins, there's noise on the voltage spanning almost 1Vpp. However these are tameable.</p>
<p class="caption"> Motor noise and startup behavior </p>
<p><img alt="Motor noise and startup behavior" class="addpic" src="/static/igt2_motorstartupnoise.jpg"/></p>
<h3>Motor protections</h3>
<p>A motor is a set of coils that magnetize when current goes through them, allowing them to push off a set of permanent magnets in their center and generate the torque that they're known for. Whenever we turn a motor off suddenly, we essentially stop pushing current through a large inductor, their coils. This inductor then tries to preserve the current going through it by flipping the voltage across it. In practice, this means turning off a motor briefly creates a massive spike of both positive and negative voltage. </p>
<p class="caption"> Motor turn off creates a big spike of voltage </p>
<p><img alt="Motor turn off creates a big spike of voltage" class="addpic" src="/static/igt2_motorkickback.jpg"/></p>
<p>This is called kickback, and it can damage sensitive electronics like your microcontroller. To protect our ESP against kickback voltage, we connect a diode going from GND to V+. In case GND ever exceeds V+, it'll leak through the diode before it has a chance to go through our microcontroller and destroy it. </p>
<h3>Smoothing motor voltage</h3>
<p>Besides quickly fatal problems like kickback, I was also concerned about slowly fatal problems. Like that the noisy voltage of the motor would mess with the internal timings of the ESP and prevent WiFi from working properly. </p>
<p>We are taking the stardard solution to this, which is to use decoupling or filter capactiors across V+ to GND. Due to the size of the noise, nothing worked until I got to massive values of C. Here I've placed a 220uF tantalum capacitor across the V+ line and GND halfway through the oscilloscope trace. </p>
<p class="caption"> Capacitor filtering motor noise down to half the amplitude </p>
<p><img alt="Capacitor filtering motor noise down to half the amplitude" class="addpic" src="/static/igt2_filtercapworks.jpg"/></p>
<p>You can see the noise drop from a 200mV brick line down to ~100mV. Although it's not super significant, I left it in because it didn't do any harm and definitely does help smooth the noise. </p>
<h2 id="done-setting-up-the-microcontroller">Done setting up the microcontroller?</h2>
<p>It should look something like this:</p>
<p class="caption"> schematic of microcontroller for the infoglobe. </p>
<p><img alt="schematic of microcontroller for the infoglobe." class="addpic" src="/static/igt2_microwiringdiagram.jpg"/></p>
<p class="caption"> View of the bottom of the infoglobe's new brain, the ESP8266 </p>
<p><img alt="View of the bottom of the infoglobe's new brain, the ESP8266" class="addpic" src="/static/igt2_equippedesp.jpg"/></p>
<h2 id="finishing-touches">Finishing touches</h2>
<p>Once you've wired everything, tape up the inside of the AAA backup power supply so we can put our ESP in there without shorting it out.</p>
<p>Why are all those funniny blue wires going though the hole for? Patience, my dear friend. We're getting there.</p>
<hr/>
<h1 id="step-2-finding-power-on-the-infoglobe-board">Step 2: Finding power on the Infoglobe board</h1>
<p>When you first open the infoglobe, you get to see its beautiful, unmarred circuit board. We're gonna mess with it immediately. </p>
<p class="caption"> The power wires are the red ones. Yes there's 3 sets of red wires wrapped together. No I don't remember which is which. </p>
<p><img alt="The power wires are the red ones. Yes there's 3 sets of red wires wrapped together. No I don't remember which is which." class="addpic" src="/static/igt2_circuitoverhead.jpg"/></p>
<p>We know that the motor probably gets its power from one of the red wires. The only problem is, there's three of them and they're all super tangled up.
If we look closely, we notice that two of the red wires go to the bottom of the limit switch, the very piece that controls the motor turning on! As it turns out, those ARE the power wires, you just need to figure out which one is "after" the switch (off when switch off, etc.), then connect our ESP's power wire to it somehow. </p>
<p>Stripping out a section and soldering works, but alternatively, you can use the power wire to do a continuity test and find regions on the board that are connected to it. One probe on the power wire, and with the other probe you just start testing random pads on the infoglobe board. </p>
<p>Here's where I broke out the voltage. Under the safety switch you can find the V+ connections, both before and after the switch. </p>
<p class="caption"> Location of the 5.6V line we'll be using. "After switch" comes on when the safety is down. </p>
<p><img alt="Location of the 5.6V line we'll be using. &quot;After switch&quot; comes on when the safety is down." class="addpic" src="/static/igt2_powerlabeled.jpg"/></p>
<p>If you want to check, you can plug the infoglobe in and check if the motor supply voltage is what you want. It should sit at ~6-6.3V when the safety switch is off, and drop to ~5.6V with the rotor on.</p>
<p class="caption"> Supply voltage at 5.6V, rotor visibly spinning </p>
<p><img alt="Supply voltage at 5.6V, rotor visibly spinning" class="addpic" src="/static/igt2_motoronvoltage.jpg"/></p>
<p class="caption"> With the safety switch untriggered, supply voltage is over 6V </p>
<p><img alt="With the safety switch untriggered, supply voltage is over 6V" class="addpic" src="/static/igt2_motoroffvoltage.jpg"/></p>
<p>Once you've confirmed that the power lines are giving you what you want, go ahead and solder a long wire to the V+ point. Also solder a similarly long wire to a GND point of the board. You can find these all over, here's the two I used. </p>
<p class="caption"> I used the left one for power ground, and the right one to ground my data LED </p>
<p><img alt="I used the left one for power ground, and the right one to ground my data LED" class="addpic" src="/static/igt2_circuitoverhead_gndlabeled.jpg"/></p>
<p>At this point you should have 2 wires coming from the left side of the board connected to power and ground, with long enough wires to push them into the microcontroller's compartment. </p>
<hr/>
<h1 id="step-3-data-routing">Step 3: Data routing</h1>
<p>All that's left is to reroute the data wires that we made earlier. </p>
<p>Earlier when our computer powered the microcontroller, the microcontroller didn't share a ground with the infoglobe so we had to cut both power and GND on the infoglobe's data LED. Now that the microcontroller is sharing the Infoglobe ground, we can ground the LED near where it stands and just run one wire to the microcontroller, avoiding another messy wire.</p>
<p>If you look in the ground pic above, you'll see the right connection point is right beside the data LED. I soldered the right leg of the LED to that spot on the board through the current-limiting resistor we had on it before.</p>
<p class="caption"> Picture of the right leg of the LED (ground side) connected via resistor to ground </p>
<p><img alt="Picture of the right leg of the LED (ground side) connected via resistor to ground" class="addpic" src="/static/igt2_ledgrounded1.jpg"/></p>
<p class="caption"> Alternate view of the LED's ground side </p>
<p><img alt="Alternate view of the LED's ground side" class="addpic" src="/static/igt2_ledgrounded2.jpg"/></p>
<p>Now we have the LED grounded, we want to connect a longer wire to the LED's positive side and run that wire towards our microcontroller compartment. I just cut my old wire and soldered on a longer bit, which can be seen in the pictures above.</p>
<p>After you've done this, the data LED should have one long wire coming out of it and the power area should have two. Picture below, but the wires need to be elongated to reach the compartment comfortably.</p>
<p class="caption"> Completed wiring overhead shot, but the wires are all too short </p>
<p><img alt="Completed wiring overhead shot, but the wires are all too short" class="addpic" src="/static/igt2_completewiring.jpg"/></p>
<h1 id="step--optional-add-a-power-switch"><s>Step ? (optional): Add a power switch</s></h1>
<p><b>EDIT - I don't think this mod is necessary anymore. I was worried that powering the ESP with my computer while it was still connected to the motor's power would run 5V to the motor and and make it spin, or else damage the motor by underpowering it in some way. I checked the resistance across the motor and it seemed pretty high, so I'm no longer worrying about this. </b></p>
<p><s>Since I wanted to be able to change the code on my ESP after putting it all back together, I connected the microcontroller to power through a little click switch that I left in the bottom. I had to burn a second hole through the casing to do this, but I think it's necessary </s></p>
<p class="caption"> Power switch for ESP </p>
<p><img alt="Power switch for ESP" class="addpic" src="/static/igt2_powerswitch.jpg"/></p>
<!-- - explain flyback - online graphic -->
<!-- - explain capacitor - 2x images of noise before and after if we have them, otws dwai -->
<!-- - explain compartment photo -->
<!-- - circuit diagram?  -->
<!-- - icture for searching for power -->
<!-- - picture of voltage on the lines -->
<!-- - picture of power before and after the switch -->
<!-- - picture of startup kicking -->
<!-- - overhead shot of the power connections and where the wires are going and overall -->
<!-- # Step 2: Data -->
<!-- - picture for LED ground connection -->
<!-- - picture for wiring high line, leave it long -->
<!-- - overhead shot of the data connections -->
<!-- - burn hole for the data wires -->
<h1 id="step-4-finish-routing-the-wires-to-the-compartment">Step 4: Finish routing the wires to the compartment</h1>
<p>You'll need your soldering iron again for this part. The battery compartment at the bottom of the infoglobe does not have a hole to the inside, so we're going to add one by melting the plastic with our soldering iron. If you have a drill, it'll work but probably not well. </p>
<p>The spot I chose is next to the safety switch on the left side of the Infoglobe, and is the only side of the battery compartment visible from the top without removing the PCB. Clear the area of wires and make sure the other side is clean, then get to a soldering filter fan. You'll just push the soldering iron tip into the plastic casing slowly, and voila! A hole for our wires!</p>
<p class="caption"> Internal hole for wires going to the microcontroller </p>
<p><img alt="Internal hole for wires going to the microcontroller" class="addpic" src="/static/igt2_burnhole_inside.jpg"/></p>
<p class="caption"> Corresponding external hole </p>
<p><img alt="Corresponding external hole" class="addpic" src="/static/igt2_burnhole_outside.jpg"/></p>
<p>We are gonna push three wires through this hole, mine happened to all look the same so I had to mark each one so I could tell them apart. On the other side, you'll solder the power to 5V, ground to GND, and the data line to D2 or whatever pin you're using in your code to control the infoglobe.</p>
<p>Your microcontroller should sit nicely if it's got the Wemos D1 Mini shape. </p>
<h1 id="step-7-put-everything-back-together">Step 7: Put everything back together.</h1>
<p>Secure the wires internally, then put the grey shell back on, then screw the rotor back on, then put the dome back on. </p>
<p><img alt="" class="addpic" src="/static/igt2_tolife.jpg"/></p>
<p>And we're done! You are now the proud owner of a modded Infoglobe. Now get out there and make it say some cool stuff!</p>
<h1 id="cya-later">Cya later!</h1>
<p>Let me know if I've left out details or you need any clarification, my email is listed above.</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/infoglobetutorial2/</guid>
      <pubDate>Sat, 15 Oct 2022 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Making clear gelatin from porcine sheets</title>
      <link>https://andykong.org/blog/cleargelatin1/</link>
      <description>From Jello you came, and to Jello you shall return</description>
      <content:encoded><![CDATA[<html><body><p>Hello! I have recently moved to Switzerland. I'll be writing some smaller posts to encourage myself to get back into this blogging thing, and this is the first. We'll see if it works.</p>
<p>Before coming to Switzerland, I thought a bit about the things I'd miss about America. According to forum posts from other expats, the Swiss had no mint chocolate (still true), nor peanut butter (untrue as of now), nor chocolate chips (still true), nor Jell-O and some of the other brands of random snacks. That's all fine and good since most snacks have substitutes, but Jello??? There's no equivalent for this clear, jiggly snack!</p>
<p class="caption"> Look at that Joggly  </p>
<p><img alt="Look at that Joggly " class="addpic" src="/static/cg1_jellohero.jpg"/></p>
<p>Luckily, I was able to find some sheets of pure-ish gelatin in the supermarket baking section, which is (I think) the core ingredient of Jello. Since I've only ever had colored, sweetened Jello, I decided my first time making it I would see how gross the unsweetened version is, as well as checking the clarity of it without any coloring.</p>
<p class="caption"> Partially inspired by my discovery that science classes will use gelatin casts to teach optics </p>
<p><img alt="Partially inspired by my discovery that science classes will use gelatin casts to teach optics" class="addpic" src="/static/cg1_jellolens.jpg"/></p>
<hr/>
<h1 id="in-sheets">In sheets???</h1>
<p>The store sells gelatin in both powder and sheets form, and I chose the sheets cause I found the form factor interesting. Each sheet is good enough for around 100mL of liquid, and is totally clear with some bubbles at the seams.</p>
<p class="caption"> Clear gelatin sheets fresh from the store </p>
<p><img alt="Clear gelatin sheets fresh from the store" class="addpic" src="/static/cg1_composite.jpg"/></p>
<p>The sheet is pretty cool. It's embossed in a diamond pattern (zoom in!) and is quite bendy (not brittle at all) and hard to the touch. And when I touched it, it didn't get sticky or anything, so my skin moisture did not sufficiently wet it. I put it in my mouth for 30 sec and it became only a bit softer, and had no flavor. I'm using a 500mL tupperware to hold my jello, so I used around 4 sheets and left some air in the top. </p>
<p>First, you gotta soak the gelatin sheets in cold water for 5-10 minutes. This is said to activate or "bloom" it. Supposedly putting it straight into hot water will swell the outside without letting the inside activate, but that sounds like untested baloney. </p>
<p class="caption"> Soaked gelatin sheet is like a clear tisseue </p>
<p><img alt="Soaked gelatin sheet is like a clear tisseue" class="addpic" src="/static/cg1_soaked.jpg"/></p>
<p>While that was happening I went and microwaved my tupperware to get the water warm. I brought over the gelatin, which had sagged down in the cup of water and got ready to transfer it. I used a spoon because I didn't know how my hand oils would affect the setting. </p>
<p><img alt="" class="addpic" src="/static/gc1_bothcups.jpg"/></p>
<p>The package said to stir until totally dissolved and I had worried that it would take a long time, but it dissolved on impact into this slightly tan cloud. I stirred it for just a few seconds and it incorporated just fine. Here's my tupperware chilling in the fridge right after this</p>
<p class="caption"> Unset gelatin </p>
<p><img alt="Unset gelatin" class="addpic" src="/static/gc1_preset.jpg"/></p>
<p>On my first attempt, it didn't set right because I misread the instructions. I thought each sheet could set 1L, but that was off by an order of magnitude (I never claimed to read French). I corrected that error, and after a night in the fridge, the properly-set gelatin turned cloudy.</p>
<p class="caption"> Set gelatin </p>
<p><img alt="Set gelatin" class="addpic" src="/static/gc1_postset.jpg"/></p>
<p>The gelatin is super stiff after setting, and on measuring, I realized I used 4 sheets for around 300g of water. This is a good ratio for future gelatin — I hate squishy Jell-O.</p>
<p class="caption"> Me holding the gelatin. If it looks fun to hold, that's because it is </p>
<p><img alt="Me holding the gelatin. If it looks fun to hold, that's because it is" class="addpic" src="/static/gc1_meholding.jpg"/></p>
<hr/>
<h1 id="flavor">Flavor</h1>
<p>The taste is completely neutral. The mouthfeel is that of Jell-O and there is a tiny or no taste added from the gelatin, but it feels wrong to be eating water. It's really weird!</p>
<p>Since you can just reheat gelatin to make it liquid again, I made another batch with sugar. I heard that sucrose actually helps the gelatinization process, and wanted to check if that was true. I must say I didn't notice it being any stiffer, but it definitely tasted much better. </p>
<h1 id="laser">Laser?</h1>
<p class="caption"> Green laser experiment in gelatin </p>
<p><img alt="Green laser experiment in gelatin" class="addpic" src="/static/cg1_laser.jpg"/></p>
<p>Finally, the reason that one of the tags is "laser" is because I wanted to see if the sugar gradient experiment would still work if the sugar gradient were solidified in gelatin. Spoiler: It does! Still bends light as we expect it to. </p>
<p>I think the diffusion of the sugar still happens, so eventually this block of gelatin won't bend lasers anymore. But I'll let you know how it looks later if I make a followup for this. </p>
<p class="caption"> A red laser also bends, but much less visibly (look at the spot on the table on the right, this is the bent output of the red laser). I heard adding a small amount of creamer to the water helps the red show up, but I'll try that later. </p>
<p><img alt="A red laser also bends, but much less visibly (look at the spot on the table on the right, this is the bent output of the red laser). I heard adding a small amount of creamer to the water helps the red show up, but I'll try that later." class="addpic" src="/static/cg1_laser2.jpg"/></p>
<hr/>
<h1 id="future-work">Future Work</h1>
<ol>
<li>Forgot to try the laser beam bending downwards, which is why I did this in the first place! Re-setting it now and seeing if it will bend down as well (probably will)</li>
<li>I wonder if the gelatin will set at room-temperature? I want to extend the time it takes to set, to allow a more impressive sugar gradient to form. Even if it doesn't set at room temperature, we can use use this for our purposes. Dissolve the gelatin and let the sugar gradient form outside the fridge, and only then put it in the fridge to set.</li>
<li>I wonder if the sugar gradient will slowly diffuse into the gelatin, negating the purpose of our gelatin in the first place? We can use saran wrap to avoid this though.</li>
</ol>
<p>I'm trying to make a circular laser, and I fear this is easily done with some hose and gelatin instead of this complex ass setup i'm envisioning. Perhaps this will be cooler though.</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/cleargelatin1/</guid>
      <pubDate>Tue, 04 Oct 2022 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Infoglobe Tutorial Pt 1 — Hardware Prototyping</title>
      <link>https://andykong.org/blog/infoglobetutorial1/</link>
      <description>Hacking a new brain into the Infoglobe</description>
      <content:encoded><![CDATA[<html><head><style>
    image{
        max-width: 100%;
    }
</style>
</head><body><p>Hello! I'm writing a tutorial on how to hack your own Olympia Infoglobe. This first part just tells you all the wires you'll need to cut or connect in order to make it display custom messages. </p>
<p class="caption"> caption </p>
<p><img alt="caption" class="addpic" src="/static/igt1_hero.jpeg"/></p>
<p>If you don't already know about the Infoglobe (surprising considering how I won't shut up about it), it's a caller ID system from the turn of the millenia which displayed who was calling your quaint little landline phone. It could also store messages that people left you, and display holiday messages. Overall, a cute little device!</p>
<p>Today, the humble Infoglobe has been rendered obsolete by the modern smartphone. With no landline resurgence in sight, we're going to hack the Infoglobe to show custom messages and give it a second life as a weather station or some other mundane job.</p>
<p>Let's start!</p>
<hr/>
<h1 id="tools">Tools</h1>
<p>You'll need the following things</p>
<ul>
<li>1x Infoglobe</li>
<li>Philips head screwdriver</li>
<li>wire cutter</li>
<li>soldering iron + solder</li>
<li>50-1kΩ resistor</li>
</ul>
<p class="caption"> Since I already did this hack, I have only photographed two of the tools. Please use your imagination for the rest </p>
<p><img alt="Since I already did this hack, I have only photographed two of the tools. Please use your imagination for the rest" class="addpic" src="/static/igt1_tools.jpg"/></p>
<h2 id="step-1-remove-the-screws-from-the-base">Step 1: Remove the screws from the base</h2>
<p>Pretty straightforward. There's 4 Philips-head screws in these 4 dark holes. They're kinda deep in there, so get a long thing screwdriver. Nothing will happen when they come out, but it makes the next step easier. </p>
<p class="caption"> The four screw burrows, helpfully circled in red </p>
<p><img alt="The four screw burrows, helpfully circled in red" class="addpic" src="/static/igt1_screws.jpg"/></p>
<h2 id="step-2-remove-the-clear-blue-dome">Step 2: Remove the clear blue dome</h2>
<p>Now we're taking off the top and getting to the insides. The top is held on by four latches you can see from the outside on the edges.</p>
<p class="caption"> Latches visible from the outside (pic from eBay) </p>
<p><img alt="Latches visible from the outside (pic from eBay)" class="addpic" src="/static/igt1_latches.jpeg"/></p>
<p>Inside, they have a little notch going from the wall radially inwards</p>
<p class="caption"> Inside the base, there are notches that go towards the center </p>
<p><img alt="Inside the base, there are notches that go towards the center" class="addpic" src="/static/igt1_bottomclasp.jpg"/></p>
<p>The top part has these seatbelt-like shapes that line up with the notch on the inside. </p>
<p class="caption"> And on the top part, there are latches that extend into the body </p>
<p><img alt="And on the top part, there are latches that extend into the body" class="addpic" src="/static/igt1_topclasp.jpg"/></p>
<h3>Careful!!</h3>
<p>I won't lie to you: getting this thing off is kinda hard. You feel like you're gonna break it on accident, and it's a sphere so it's hard to hold on to, and will definitely break when you drop it. I recommend sitting on the floor or hugging it when you're wrestling the top part off. </p>
<p>You'll want to pick one latch to start with. Right above the latch on the blue part, pull inwards and pull up at the same time. Hold the base tightly with your other hand, or hug it with your arm. A wedge/screwdriver is unlikely to help. Here's a video of me getting the first one out.</p>
<video controls="" src="/static/igt1_firstlatch.mp4" style="max-width: 100%"></video>
<p style="font-size:.7em;">Funnily enough, I'm wearing a Dome shirt as I open this dome</p>
<p>The rest of the latches get easier after the first two. Same technique, just pull in and pull up at the same time. </p>
<h1 id="step-3-remove-the-propeller">Step 3: remove the propeller</h1>
<p>Once you're in, the grey part will be loose, but still held in by the spinning arm of the Infoglobe. We'll have to take that off. Just use your screwdriver to remove those 3 small screws. The propeller is spring loaded, so maybe unscrew all of them a little first so the first one doesn't bounce out and get lost</p>
<p class="caption"> Note the square center peg and the triangular screw arrangement - automatic alignment! </p>
<p><img alt="Note the square center peg and the triangular screw arrangement - automatic alignment!" class="addpic" src="/static/igt1_propellerscrews.jpg"/></p>
<p>Remove both the propeller and the grey plate and set them aside. </p>
<h1 id="step-4-hijack-the-infoglobes-data-led-and-add-a-currentlimiting-resistor">Step 4: Hijack the Infoglobe's data LED and add a current-limiting resistor</h1>
<p>There's 3 obvious LEDs once you're looking at the circuit board. One is red, this is just a power indicator. Of the two other light-blue ones, the innermost one is the data LED. </p>
<p class="caption"> All LEDs inside the infoglobe, labeled </p>
<p><img alt="All LEDs inside the infoglobe, labeled" class="addpic" src="/static/igt1_dataLED.jpg"/></p>
<p>Previous tutorials have made circuit boards that integrate into the infoglobe, allowing it to continue displaying the phone stuff you wanna see plus cool custom stuff. This is why past projects have had such complex setups. I say why bother? The data LED has two wires like any other LED, and if we cut them then we can write whatever we want. </p>
<p>There's two ways about this. You can either cut both legs of the LED and solder on new wires that run to the outside of the infoglobe, or you can just cut the positive side of the LED and connect the grounds of the Infoglobe board to your microcontroller. </p>
<p>Either way, two wires get added so it's really up to you. I started with the single wire and joined the grounds first because I knew I wanted to power my microcontroller off the infoglobe power eventually, and it made prototyping way faster. </p>
<p class="caption"> The two-cut approach hijacks the entire data LED. The LED's positive side is on the left </p>
<p><img alt="The two-cut approach hijacks the entire data LED. The LED's positive side is on the left" class="addpic" src="/static/igt1_bothcut.jpg"/></p>
<p class="caption"> The one cut approach, hijacks only the power wire (left) and requires you find ground somewhere else (anywhere connected to the right wirse is fine) </p>
<p><img alt="The one cut approach, hijacks only the power wire (left) and requires you find ground somewhere else (anywhere connected to the right wirse is fine)" class="addpic" src="/static/igt1_onecut.jpg"/></p>
<p>Run those two wires outside of the casing and connect a resistor in series so you don't blow out the Infoglobe's infrared LED. </p>
<h3>PLEASE CONNECT A SERIES RESISTOR TO THE DATA LED LINE BEFORE USING IT, OTHERWISE YOU RISK BURNING OUT THE LED!</h3>
<hr/>
<h1 id="testing-the-infoglobe-without-putting-it-back-together">Testing the Infoglobe without putting it back together</h1>
<p>If you wanted to plug in the infoglobe with all the plastic off, it'd probably be dangerous for both the board and you. It just takes a hair caught in the rotor to ruin your Infoglobe and hair and project all at once. But it sucks to put it all back together, and there is a way to be relatively safe without doing that. </p>
<p>1) Put the grey piece back on. The alignment is a bit tricky but it should drop right in</p>
<p><img alt="" class="addpic" src="/static/igt1_reass1.jpg"/></p>
<p>2) Then place the rotor back on the center section and screw it in. Make sure you screw them all in a bit simultaneously, since there is a spring under it. </p>
<p><img alt="" class="addpic" src="/static/igt1_reass2.jpg"/></p>
<p>3) Get something to fool the safety switch, preferably nonmetal</p>
<p class="caption"> This limit switch needs to be taped or pushed down </p>
<p><img alt="This limit switch needs to be taped or pushed down" class="addpic" src="/static/igt1_safeswitch.jpg"/></p>
<p class="caption"> Chopstick works for depressing it </p>
<p><img alt="Chopstick works for depressing it" class="addpic" src="/static/igt1_reass3.jpg"/></p>
<p>The top dome can also be placed into the slots without clicking down, but still low enough to trigger the safety switch. This is probably the safest way to do it.</p>
<p class="caption"> The lid also works for triggering the safety switch, but this is the only method that is actually safe </p>
<p><img alt="The lid also works for triggering the safety switch, but this is the only method that is actually safe" class="addpic" src="/static/igt1_reass4.jpg"/></p>
<p>4) Plug in the infoglobe power, and the rotor should begin spinning with no words appearing</p>
<p>To change the insides requires at least unscrewing the rotor, but luckily you won't need to do it much at all. </p>
<hr/>
<h1 id="final-setup">Final setup</h1>
<p>At this point, you should have an Infoglobe with two wires coming out of it. I originally used F jumpers as the wires to the data LED, then plugged into them using an external Arduino. </p>
<p class="caption"> Picture of the Arduino setup I originally worked with, Arduino not in-frame </p>
<p><img alt="Picture of the Arduino setup I originally worked with, Arduino not in-frame" class="addpic" src="/static/igt1_testingsetup2.jpg"/></p>
<p>Later when I was sure the code worked on Arduino, I moved to using an ESP8266 microcontroller. It's the little square with a blue light in the picture below. </p>
<p><img alt="" class="addpic" src="/static/igt1_testingsetup.jpg"/></p>
<p>Since the ESP has no headers, I'm using a breadboard to connect the ESP to the data wires. Again, the LED's wires you've added will just run out from under the shell of the Infoglobe, and they safety switch can be engaged using the top dome despite the wires preventing it from closing fully. </p>
<p>There is some more hardware on the breadboard than I'm letting on, but that will be covered in Pt 2.</p>
<hr/>
<h1 id="conclusion">Conclusion</h1>
<p>There's a few more steps if you want to integrate the infoglobe with your microcontroller, but that's for a later tutorial. Now you've gotten a connected LED. Time to write some messaging software!</p>
<p>If I haven't published it and you want me to get on with it, just shoot me an email and I'll add it to my very relaxed schedule. </p>
<p>Update 10/13: There's a part two <a href="../infoglobetutorial2">here</a>!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/infoglobetutorial1/</guid>
      <pubDate>Sat, 01 Oct 2022 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Setting up Electric Tables v0.2</title>
      <link>https://andykong.org/blog/settingupelectrictables/</link>
      <description>Shoutout to Tom Critchlow</description>
      <content:encoded><![CDATA[<html><body><p>Hello everyone! I've recently been thinking about how much time I spend on my computer, and I've gotten gotten into activity logging so I can reflect on the countless hours spent on here. There are other tools out there, but many are manual and many are paid, and I just can't be bothered with either. </p>
<hr/>
<h2 id="browser-as-a-memex">Browser as a Memex</h2>
<p class="caption"> Screenshot from my Timing dashboard, back in my period of riches </p>
<p><img alt="Screenshot from my Timing dashboard, back in my period of riches" class="addpic" src="/static/rt_timingdashboard.png"/></p>
<p>Over half of all my computer activity happens in the browser, so I've decided to start with indexing browser activity automatically. </p>
<p><a href="https://tomcritchlow.com/">Tom Critchlow</a> recently released this great bookmarklet tool he calls <a href="https://tomcritchlow.com/2022/02/07/electric-tables-v2/">Electric Tables</a>, which template matches onto author names, titles, dates, and more to index a website as a row in a spreadsheet. Previously, this used the browser's <code>localStorage</code>, but it has since moved to Google Sheets. </p>
<p>I think this is a great development, and wanted to set up Electric Tables myself. The <a href="https://gist.github.com/tomcritchlow/cbb06a9298fb6cc0804372552fda1f96">gist</a> that Tom posted with the code had instructions for each step of the way, but silly coder I am, I ran into some trouble setting it up. This post is to clarify what exactly needs to be done so future Andy (and others!) can have less trouble in the future. </p>
<p>If you're reading this, thanks Tom! </p>
<h1 id="lets-begin">Let's begin!</h1>
<p>So, theres three things to set up: the Google Script, the Google Sheet, and the Javascript bookmarklet. </p>
<hr/>
<h2 id="google-script">Google Script</h2>
<p>Go to <a href="https://script.google.com/home">script.google.com</a> and sign in, then click "New project" in the top left. It should take you to this blank page</p>
<p class="caption"> Blank Google Script </p>
<p><img alt="Blank Google Script" class="addpic" src="/static/et_googlescript1.png"/></p>
<p>Name your project, then paste in the code from Tom's <a href="https://gist.github.com/tomcritchlow/cbb06a9298fb6cc0804372552fda1f96">gist</a> under <code>electrict-tables-v0.2.gs</code>. Feel free to delete the starter <code>myFunction</code>. </p>
<p>We need to add a dependency called Cheerio which scrapes the text from websites. Hit the plus icon next to "Libraries" on the left panel, and include <a href="https://github.com/tani/cheeriogs">Cheerio</a> by looking up the Script ID: <code>1ReeQ6WO8kKNxoaA_O0XEQ589cIrRvEBA9qcWpNqdOP17i47u6N9M5Xh0</code>. Hit "Add" to include it.</p>
<p class="caption"> Google Script with code, looking up Cheerio successfully </p>
<p><img alt="Google Script with code, looking up Cheerio successfully" class="addpic" src="/static/et_googlescript2.png"/></p>
<p>Now we just gotta publish it to the web. Click "Deploy -&gt; New deployment", and select "Web app" as the type. I don't think the type matters for functionality, and you can leave the options as default. </p>
<p>When you click deploy, you'll get a permissions popup screen. Since only you can run this script, it's safe to allow it to edit your spreadsheets, so go ahead and give it permission.</p>
<p>If you go to Deploy-&gt;Manage deployments, you can grab the Web app URL. We'll need this for our bookmarklet. </p>
<p class="caption"> Our script's API endpoint URL </p>
<p><img alt="Our script's API endpoint URL" class="addpic" src="/static/et_googlescript3.png"/></p>
<hr/>
<h2 id="google-sheet">Google Sheet</h2>
<p>Create a new Google Sheet, making sure it's under the same Google account as your script. We're going to add headers to the file so added rows are properly formatted. The column names are case-sensitive!</p>
<p class="caption"> A properly set up Google Sheet </p>
<p><img alt="A properly set up Google Sheet" class="addpic" src="/static/et_googlesheet.png"/></p>
<p>I've also highlighted the Sheet ID in the URL. We only need this bit for the bookmarklet, not the whole thing. </p>
<hr/>
<h2 id="bookmarklet">Bookmarklet</h2>
<p>Last step, the actual trigger for adding pages to Electric Tables. Open your favorite text editor, and paste in <code>bookmarklet.js</code> from Tom's <a href="https://gist.github.com/tomcritchlow/cbb06a9298fb6cc0804372552fda1f96">gist</a>. </p>
<p>You only need to add the macro URL from step 1 (should look like "<a href="">script.google.com/macros/s/....../exec</a>") and the spreadsheet ID (should look like <code>AodN89ua98dWL12O1oidRTa...</code> and be really long)</p>
<p>Then head over to a <a href="https://caiorss.github.io/bookmarklet-maker/">bookmarklet generator</a> and copy in your edited <code>bookmarklet.js</code>. Hit "Run Code" to test it out, and the Electric Tables menu should pop up in the top right of your screen. Add a note, then click "Submit". </p>
<p class="caption"> Bookmarklet page getting indexed </p>
<p><img alt="Bookmarklet page getting indexed" class="addpic" src="/static/et_bmtesting.png"/></p>
<p>Tab over to your Google Sheets page, and you should see a new row with the bookmarklet site and your note appear after a second.</p>
<p class="caption"> Successful Electric Tables entry! </p>
<p><img alt="Successful Electric Tables entry!" class="addpic" src="/static/et_newrow.png"/></p>
<p>Celebrate! Now you can just drag the bookmarklet button onto your bookmarks bar to make a button for it. You can also create a bookmark, then for the URL paste in the "Output" Javascript function. </p>
<p class="caption"> Adding bookmarklet manually </p>
<p><img alt="Adding bookmarklet manually" class="addpic" src="/static/et_handbookmark.png"/></p>
<hr/>
<h1 id="wrapping-up">Wrapping Up</h1>
<p>So, you should have a nifty little bookmark sitting in your bookmarks bar that catalogues interesting pages you find for later, and it's all stored on Google's big boy servers! No need to remember it all yourself anymore (as if you were doing that before 😜)</p>
<p class="caption"> Electric Tables bookmarklet </p>
<p><img alt="Electric Tables bookmarklet" class="addpic" src="/static/et_bookmarklet.png"/></p>
<p>Happy searching!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/settingupelectrictables/</guid>
      <pubDate>Fri, 22 Apr 2022 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Supercapacitor To Lithium Battery Converter</title>
      <link>https://andykong.org/blog/supercap2lipo/</link>
      <description>To enable powering all lithium battery devices with chunky supercaps instead</description>
      <content:encoded><![CDATA[<html><body><p>This past weekend, I attended my last Build18 as an undergrad. Build18 is a hardware hackathon at CMU which gives student teams a $300 budget to buy any parts they want, to build any project they'd like. This time around, I decided to convert a smartphone to use a supercapacitor instead of its lithium battery. The benefit of a supercapacitor is that it's easier to hold, and that it can charge around 100x faster than a lithium battery. The only downside is that it's massive. </p>
<p class="caption">Size of the supercap, here charging my FitBit</p>
<p><img alt="Size of the supercap, here charging my FitBit" src="/static/cap2lipo_capsize.jpg"/></p>
<p>To replace the lipo, I needed to convert supercapacitor voltages (0-3V) to lithium battery voltages (3.7-4.2V). The most common boost/buck-boost circuits online start working at 3V, which is just out of reach of all supercaps, so I made custom boost converter board based on an application note from the <a href="https://www.analog.com/en/products/ltc3124.html">LTC3124</a>. This post tests a few metrics of this board. </p>
<p>Most consumer electronics use lipos, and by making this board, I hope to make any future device supercap conversions easy to do. The boost converter circuit keeps working down to 0.5V, and only kicks in around 1.7V. </p>
<p class="caption">Supercap board alone</p>
<p><img alt="Supercap board alone" src="/static/cap2lipo_board.jpg"/></p>
<h1 id="testing-metrics">Testing Metrics</h1>
<p>The metrics I'm interested in are maximum continuous current output and efficiency.</p>
<p>The problem with powering a former-LiPo device with a supercap is that the boost converter bottlenecks the current. Even if the supercap can handle a device spike of 2 amps, the boost converter may not be able to, sending the voltage crashing. The maximum continuous current output lets me know which devices can use this converter board. </p>
<p>The supercap's low energy density also means that the cap alone is only 2/3 or so of the total energy in a smartphone battery, and is only made worse by the inefficiency in the boost converter. The efficiency lets us get a measurement of how much current we can expect to use from the supercap.</p>
<h1 id="setup">Setup</h1>
<p class="caption">Setup for testing the board</p>
<p><img alt="Setup for testing the board" src="/static/cap2lipo_testsetup.jpg"/></p>
<p>I used a DC  electronic load and a 3V/3A power supply for testing, and this board is set to output 4V. Here's the data:</p>
<p class="caption">Maximum continuous current out at 4V output</p>
<p><img alt="Maximum continuous current out at 4V output" src="/static/cap2lipo_currentout.png"/></p>
<p class="caption">Efficiency at 4V for various input voltages</p>
<p><img alt="Efficiency at 4V for various input voltages" src="/static/cap2lipo_efficiency.png"/></p>
<p>Considering the cap's total power scales with $V^2$, we are well above 70% efficiency for 90% of the energy of the cap. Also, the 0.6A current output at 1.5V is enough to power a smartphone, since most of the time they draw 200-500mA (except on startup). </p>
<h1 id="comparison">Comparison</h1>
<p>Crudely, the efficiency cuts down the capacity by 75%, and it only works (for smartphones, depends on current load) down to around 1.25V. At peak, the supercap is 2.85V, so we can use 1.25^2/2.85^2 = 80.7% of the power in the cap. This means when we calculate the equivalent lipo capacity of a supercap, we need to downrate it to 60% of total capacity. Ouch!</p>
<p>Could another boost converter be better? Well, the 80% downgrade is inherent to the supercapacitor at certain current output needs, and I don't think many boards can boost and output &gt;0.5A below 1.25V. I did see one more TI part which was out of stock for two years though, and it can do a bit higher current. The 75% efficiency can be better, but not at high current draws, and is capped at 90%. A realistically perfect converter would yield around 90%x90% = 80% of the supercapacitor power, which is a lot more than this board's 60%. However, this converter board works pretty well, and it's not like supercapacitors don't already have a supply issue already. </p>
<h1 id="conclusion">Conclusion</h1>
<p>One day I will get my hands on that TPS chip, but until next time, cya!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/supercap2lipo/</guid>
      <pubDate>Mon, 14 Feb 2022 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Replacing Timing.app</title>
      <link>https://andykong.org/blog/replacingtiming/</link>
      <description>Doing self-logging</description>
      <content:encoded><![CDATA[<html><body><p>I have been working on replacing a piece of software I use on a weekly basis. The Mac app <a href="https://timingapp.com/">Timing</a> is a computer context tracker, which is to say it logs what windows have been open as long as you're using it. It even offers you a beautiful interface to interact with your data through.</p>
<p class="caption">My Timing history for the past year</p>
<p><img alt="Screencap of my Timing history for the past year" src="/static/rt_timingdashboard.png"/></p>
<p>Timing has cost me $8.50 a month for the past 6 months, and they primarily target freelancers who bill clients by the hour. Unfortunately, I am not a contractor, and Timing does not help me track hours. Timing just offers me a cool composite of time as I've spent it on my laptop in the past day/week/month. And though I can't justify its cost,  I still love the functionality. So I've been trying to replace it. </p>
<h1 id="general-plan">General plan</h1>
<p>Timing calls itself a time tracker, but I don't think that's really what I want. I want to know how I'm using my laptop during the course of a day, and make adjustments based on whatever I happen to want to change.</p>
<p>Really, I just wanted to make a background-running shell or Python script that logs my context on the computer, then shows me the aggregated stats once in a while. </p>
<p>To understand what I'm doing on my computer, I think you need to know A) the current time, B) what I'm listening to, and C) which application is open and for how long. I think these give you a pretty clear picture of my mental state. For example, if I have docs open, I'm probably writing. If I'm on Youtube, I'm watching videos. If I'm on preview, reading. Of course there are exceptions — I could be on YouTube watching gaming videos or electrical engineering tutorials.But it should be good enough. Logging my music will also help me conclusively decide if music helps or harms me when I'm working. </p>
<h2 id="improvements-over-timing">Improvements over Timing</h2>
<p>My main complaint with Timing is that it didn't ever show me aggregated features pulled out of my data. I don't mean the "automagical insight extraction" that so many data analysis teams claim to implement for millions of dollars. The human brain can do all of that truly automatically, as long as the right data is shown routinely. I simply wanted something showing my usage to establish a feedback loop, and let myself decide what needs to change or not, and how that change has been going historically. </p>
<h1 id="whatve-you-done-so-far">What've you done so far?</h1>
<p>So far, I've cancelled my Timing subscription and written a pretty crude day-by-day activity tracker using Applescripts, Bash, and Python. I'm gonna refer to it as Casey for now, just to have a name for it. </p>
<p><br/><br/></p>
<h2 id="as-a-logger-casey-uses-only-15-of-the-storage-of-timing-while-being-50-more-efficient">As a logger, Casey uses only 15% of the storage of Timing while being 50% more efficient</h2>
<p><br/><br/></p><hr/>
<h2 id="performance">Performance</h2>
<p>Here's a photo of the two programs in the activity monitor, monitoring the same stretch of 16 minutes.  </p>
<p class="caption">CPU time for Timing</p>
<p><img alt="CPU time for Timing" src="/static/rt_cputesttiming.png"/></p>
<p>vs.</p>
<p class="caption">CPU time for Casey</p>
<p><img alt="CPU time for Casey" src="/static/rt_cputestcasey.png"/></p>
<p>We see the CPU time of Casey is 5.66 seconds compared to Timing's 7.68, around a 25% reduction. </p>
<p>The memory footprint of Casey is also lower, sitting at 27.4 MB vs. 48.1 MB for Timing, representing a savings of 22.2%. </p>
<hr/>
<h2 id="storage">Storage</h2>
<p>Casey retrieves user context every second, and accumulated 1033 new lines and 179kB of extra storage during the test. Each record takes up around 173 bytes pre-compression. After compression, the total storage is only 6kB, putting each log entry at 5.8 bytes for Casey. </p>
<p><img alt="Compressed and uncompressed log sizes for Casey, Finder screenshot" src="/static/rt_stotestcasey.png"/></p>
<p>I planned on comparing this to Timing's 12kB + 235kB in the sync.db and wal.db respectively (they do not immediately store it into a sqlite database), assuming the same logging rate.  But I personally can't believe that Timing could be so terribly inefficient. At 239 bytes/log, it's worse than Casey when naively storing everything as text directly. I'll instead refer to the 2400 hours of data it has recorded into a storage folder only 320MB in size, including backups. This means it sits at 2346 hours/320.8 MB = 38 bytes/sec. A much more reasonable quantity, still beaten handily by Casey's 5.8 bytes/log.</p>
<p class="caption">My Timing records and how much space they take up</p>
<p><img alt="My Timing records and how much space they take up" src="/static/rt_stotimingrecords.png"/></p>
<h1 id="shortcomings">Shortcomings</h1>
<p>Casey does not yet have a GUI, or a plan for long-term backup storage. The lookup and indexing system is still not in place, nor is the aggregation of data. Casey also lacks the periodic reminder system that I want it to have through emails. But it's a WIP, so I'm happy to document its baby steps.</p>
<h1 id="conclusion">Conclusion</h1>
<p>I want to conclude by saying that Timing is a beautifully polished piece of software. I applaud the Timing team for making such a smooth application that does one job and does it well. But it isn't what I wanted, and it's a bit expensive for me. </p>
<p>The storage and performance don't matter so much since we're talking about such small beans (7 secs on 16 minutes is around 0.7%, 320MB vs 3.2GB is not such a big swing for a year of data), but this is my current optimization. </p>
<p>More progress tomorrow. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/replacingtiming/</guid>
      <pubDate>Sun, 09 Jan 2022 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Learning AppleScript</title>
      <link>https://andykong.org/blog/applescripts1/</link>
      <description>Several examples of AppleScripts for Spotify, current app, etc.</description>
      <content:encoded><![CDATA[<html><body><p>Hello. I've always wanted to learn AppleScript in order to replace my Mac tracking apps and save a bit of money. Over this break, I had time to do that. And it seems to be about as hard as I expected it to be, which is to say it's simple to the point of being hard to learn. Here's an example</p>
<p>Applescripts is written in this natural language sort of format. You expect that makes it easier to learn and read and write, but it doesn't unless you know the syntax. Much like how a Python aficionado would find it difficult to explain to a newbie how they know of all the different built-in functions, I find it similarly hard to learn the types and values that variables can take on in an AppleScript. </p>
<p>I've written a few atomic scripts and tips I wanted to share, and I'll show them in this post. Like usual, examples of other scripts helped me learn how to write my own. For Applescripts, I had an interesting problem that my example code wouldn't work when I began to switch in my own variables. This leads me to tip #1</p>
<h1 id="tip-1-do-not-name-your-variables-yes">Tip #1: Do not name your variables "yes"</h1>
<p>Everyone has a default name for trash variables that will be replaced by something more descriptive later, and mine happens to be "yes". However, if you name your variables yes in your Applescript, you are going to have a bad time. The script throws a funky error that doesn't mention that yes is a built-in word that cannot be assigned. </p>
<h1 id="tip-2-applescript-has-no-oop-at-least-officially">Tip #2: Applescript has no OOP, at least officially</h1>
<p>This means when you try to write a more complicated Applescript and want to be a good little SWE who uses objects and functions, you will encounter various forum posts which tell you that AS is not meant for this stuff and you should give up. Maybe I just phrased my problem poorly, but my desire to make functions is solved perfectly by subroutines, which nobody mentioned until I saw it on an Applescripts blog. </p>
<p>There are also not AppleScript blogs in the same way that there are Javascript/CSS blogs. Being Apple fans, each of the AS tutorial websites is written in a legible fashion. However they are nowhere as smooth as those CSS blogs showing live examples of X property. I understand that this is because scripts cannot be played online, but it still makes it hard when they show no pictures of what the output is supposed to look like. </p>
<p>OK, onto the examples.</p>
<h1 id="examples">Examples</h1>
<h3>Spotify Context</h3>
<!-- HTML generated using hilite.me -->
<div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%"><span style="color: #888888">############ Get current spotify usage</span>
<span style="color: #008800; font-weight: bold">on</span> <span style="color: #996633">getSpotify</span>()
    <span style="color: #008800; font-weight: bold">set</span> <span style="color: #996633">retList</span> <span style="color: #008800; font-weight: bold">to</span> {<span style="background-color: #fff0f0">""</span>}
    <span style="color: #008800; font-weight: bold">if</span> <span style="color: #007020">application</span> <span style="background-color: #fff0f0">"Spotify"</span> <span style="color: #000000; font-weight: bold">is</span> <span style="color: #996633">running</span> <span style="color: #008800; font-weight: bold">then</span>
        <span style="color: #008800; font-weight: bold">tell</span> <span style="color: #007020">application</span> <span style="background-color: #fff0f0">"Spotify"</span>
            <span style="color: #008800; font-weight: bold">if</span> <span style="color: #996633">player</span> <span style="color: #0000CC">state</span> <span style="color: #000000; font-weight: bold">is</span> <span style="color: #0000CC">playing</span> <span style="color: #008800; font-weight: bold">then</span>
                <span style="color: #008800; font-weight: bold">set</span> <span style="color: #996633">tr</span> <span style="color: #008800; font-weight: bold">to</span> <span style="color: #996633">current</span> <span style="color: #996633">track</span>
                <span style="color: #008800; font-weight: bold">set</span> <span style="color: #996633">retList</span> <span style="color: #008800; font-weight: bold">to</span> {<span style="color: #0000CC">name</span> <span style="color: #008800; font-weight: bold">of</span> <span style="color: #996633">tr</span>, <span style="color: #996633">artist</span> <span style="color: #008800; font-weight: bold">of</span> <span style="color: #996633">tr</span>, <span style="color: #996633">player</span> <span style="color: #0000CC">position</span>, (<span style="color: #996633">duration</span> <span style="color: #008800; font-weight: bold">of</span> <span style="color: #996633">tr</span>) <span style="color: #333333">/</span> <span style="color: #0000DD; font-weight: bold">1000</span>, <span style="color: #0000CC">id</span> <span style="color: #008800; font-weight: bold">of</span> <span style="color: #996633">tr</span>}
            <span style="color: #008800; font-weight: bold">end</span> <span style="color: #008800; font-weight: bold">if</span>
        <span style="color: #008800; font-weight: bold">end</span> <span style="color: #008800; font-weight: bold">tell</span>
    <span style="color: #008800; font-weight: bold">end</span> <span style="color: #008800; font-weight: bold">if</span>
    <span style="color: #003366; font-weight: bold">return</span> <span style="color: #996633">retList</span>
<span style="color: #008800; font-weight: bold">end</span> <span style="color: #996633">getSpotify</span>
</pre></div>
<p>My problem with learning from example Applescript I read online is that the examples are not clearly cross-applicable and the errors you get are not helpful. I can read this code perfectly fine, and you probably can too — but it's a trap to trick you into thinking you know how to write it! This took me like an hour because I kept naming my variable yes, but even when I stopped that it still took me a while to realize "application "x"" is not replaceable with a macro like it would be in C. </p>
<h3>Current date and time, formatted as a timestamp or filename</h3>
<!-- HTML generated using hilite.me -->
<div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%"><span style="color: #888888">########## get current date/time formatted as a sortable string</span>
<span style="color: #008800; font-weight: bold">on</span> <span style="color: #996633">date_format</span>(<span style="color: #996633">adate</span>) <span style="color: #888888">-- Old_date is text, not a date.</span>
    <span style="color: #008800; font-weight: bold">set</span> {<span style="color: #007020">year</span>:<span style="color: #996633">y</span>, <span style="color: #007020">month</span>:<span style="color: #996633">m</span>, <span style="color: #007020">day</span>:<span style="color: #996633">d</span>, <span style="color: #996633">time</span>:<span style="color: #996633">t</span>} <span style="color: #008800; font-weight: bold">to</span> <span style="color: #996633">adate</span>
    <span style="color: #008800; font-weight: bold">set</span> <span style="color: #996633">delim</span> <span style="color: #008800; font-weight: bold">to</span> <span style="background-color: #fff0f0">"."</span>
    <span style="color: #008800; font-weight: bold">set</span> <span style="color: #996633">yada</span> <span style="color: #008800; font-weight: bold">to</span> (<span style="color: #996633">y</span> <span style="color: #008800; font-weight: bold">as </span><span style="color: #BB0066; font-weight: bold">string</span>) <span style="color: #333333">&amp;</span> <span style="color: #996633">delim</span> <span style="color: #333333">&amp;</span> (<span style="color: #996633">m</span> <span style="color: #008800; font-weight: bold">as</span> <span style="color: #996633">integer</span>) <span style="color: #333333">&amp;</span> <span style="color: #996633">delim</span> <span style="color: #333333">&amp;</span> <span style="color: #996633">d</span> <span style="color: #333333">&amp;</span> <span style="color: #996633">delim</span> <span style="color: #333333">&amp;</span> <span style="color: #996633">t</span>
    <span style="color: #003366; font-weight: bold">return</span> <span style="color: #996633">yada</span>
<span style="color: #008800; font-weight: bold">end</span> <span style="color: #996633">date_format</span>
</pre></div>
<p>Here you'll note that I did not include the word "yes" as my variable, but notice how similar it is to the current variable "yada". Wonder how that happened...</p>
<h3>Get currently focused app and path to that app</h3>
<p>Remixed from <a href="https://stackoverflow.com/questions/5292204/macosx-get-foremost-window-title">Stack Overflow</a></p>
<!-- HTML generated using hilite.me -->
<div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%"><span style="color: #888888">############ Get URL and name of focused app</span>
<span style="color: #008800; font-weight: bold">set</span> <span style="color: #996633">windowTitle</span> <span style="color: #008800; font-weight: bold">to</span> <span style="background-color: #fff0f0">""</span>
<span style="color: #008800; font-weight: bold">tell</span> <span style="color: #007020">application</span> <span style="background-color: #fff0f0">"System Events"</span>
    <span style="color: #008800; font-weight: bold">set</span> <span style="color: #996633">frontApp</span> <span style="color: #008800; font-weight: bold">to</span> <span style="color: #007020">first</span> <span style="color: #007020">application</span> <span style="color: #996633">process</span> <span style="color: #007020">whose</span> <span style="color: #0000CC">frontmost</span> <span style="color: #000000; font-weight: bold">is</span> <span style="color: #003366; font-weight: bold">true</span>
    <span style="color: #008800; font-weight: bold">set</span> <span style="color: #996633">frontAppName</span> <span style="color: #008800; font-weight: bold">to</span> <span style="color: #0000CC">name</span> <span style="color: #008800; font-weight: bold">of</span> <span style="color: #996633">frontApp</span>
    <span style="color: #008800; font-weight: bold">tell</span> <span style="color: #996633">process</span> <span style="color: #996633">frontAppName</span>
        <span style="color: #008800; font-weight: bold">tell</span> (<span style="color: #007020">1st</span> <span style="color: #0000CC">window</span> <span style="color: #007020">whose</span> <span style="color: #996633">value</span> <span style="color: #008800; font-weight: bold">of</span> <span style="color: #996633">attribute</span> <span style="background-color: #fff0f0">"AXMain"</span> <span style="color: #000000; font-weight: bold">is</span> <span style="color: #003366; font-weight: bold">true</span>)
            <span style="color: #008800; font-weight: bold">set</span> <span style="color: #996633">windowTitle</span> <span style="color: #008800; font-weight: bold">to</span> <span style="color: #996633">value</span> <span style="color: #008800; font-weight: bold">of</span> <span style="color: #996633">attribute</span> <span style="background-color: #fff0f0">"AXTitle"</span>
        <span style="color: #008800; font-weight: bold">end</span> <span style="color: #008800; font-weight: bold">tell</span>
    <span style="color: #008800; font-weight: bold">end</span> <span style="color: #008800; font-weight: bold">tell</span>
    <span style="color: #008800; font-weight: bold">set</span> <span style="color: #996633">appfilepath</span> <span style="color: #008800; font-weight: bold">to</span> <span style="color: #996633">POSIX</span> <span style="color: #0000CC">path</span> <span style="color: #008800; font-weight: bold">of</span> <span style="color: #007020">application</span> <span style="color: #996633">file</span> <span style="color: #008800; font-weight: bold">of</span> <span style="color: #996633">frontApp</span>
<span style="color: #008800; font-weight: bold">end</span> <span style="color: #008800; font-weight: bold">tell</span>
</pre></div>
<h3>Get the URL of any application that has a URL or path (Finder, Preview, Chrome, Safari)</h3>
<p>Remixed from Stack Overflow, <a href="https://gist.github.com/EvanLovely/cb01eafb0d61515c835ecd56f6ac199a">[1]</a> <a href="https://stackoverflow.com/questions/12129989/getting-finders-current-directory-in-applescript-stored-as-application">[2]</a></p>
<!-- HTML generated using hilite.me -->
<div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%"><span style="color: #888888">############ Get URL of current chrome/safari/preview/finder tab</span>
<span style="color: #008800; font-weight: bold">set</span> <span style="color: #996633">currentTabUrl</span> <span style="color: #008800; font-weight: bold">to</span> <span style="background-color: #fff0f0">""</span>
<span style="color: #008800; font-weight: bold">if</span> (<span style="color: #996633">frontAppName</span> <span style="color: #333333">=</span> <span style="background-color: #fff0f0">"Safari"</span>) <span style="color: #000000; font-weight: bold">or</span> (<span style="color: #996633">frontAppName</span> <span style="color: #333333">=</span> <span style="background-color: #fff0f0">"Webkit"</span>) <span style="color: #008800; font-weight: bold">then</span>
    <span style="color: #008800; font-weight: bold">tell</span> <span style="color: #007020">application</span> <span style="background-color: #fff0f0">"Safari"</span> <span style="color: #008800; font-weight: bold">to</span> <span style="color: #008800; font-weight: bold">set</span> <span style="color: #996633">currentTabUrl</span> <span style="color: #008800; font-weight: bold">to</span> <span style="color: #996633">URL</span> <span style="color: #008800; font-weight: bold">of</span> <span style="color: #007020">front</span> <span style="color: #0000CC">document</span>
<span style="color: #008800; font-weight: bold">else</span> <span style="color: #008800; font-weight: bold">if</span> (<span style="color: #996633">frontAppName</span> <span style="color: #333333">=</span> <span style="background-color: #fff0f0">"Google Chrome"</span>) <span style="color: #000000; font-weight: bold">or</span> (<span style="color: #996633">frontAppName</span> <span style="color: #333333">=</span> <span style="background-color: #fff0f0">"Google Chrome Canary"</span>) <span style="color: #000000; font-weight: bold">or</span> (<span style="color: #996633">frontAppName</span> <span style="color: #333333">=</span> <span style="background-color: #fff0f0">"Chromium"</span>) <span style="color: #008800; font-weight: bold">then</span>
    <span style="color: #008800; font-weight: bold">tell</span> <span style="color: #007020">application</span> <span style="background-color: #fff0f0">"Google Chrome"</span> <span style="color: #008800; font-weight: bold">to</span> <span style="color: #008800; font-weight: bold">set</span> <span style="color: #996633">currentTabUrl</span> <span style="color: #008800; font-weight: bold">to</span> <span style="color: #996633">URL</span> <span style="color: #008800; font-weight: bold">of</span> <span style="color: #0000CC">active</span> <span style="color: #003366; font-weight: bold">tab</span> <span style="color: #008800; font-weight: bold">of</span> <span style="color: #007020">front</span> <span style="color: #0000CC">window</span>
<span style="color: #008800; font-weight: bold">else</span> <span style="color: #008800; font-weight: bold">if</span> (<span style="color: #996633">frontAppName</span> <span style="color: #333333">=</span> <span style="background-color: #fff0f0">"Preview"</span>) <span style="color: #008800; font-weight: bold">then</span>
    <span style="color: #008800; font-weight: bold">tell</span> <span style="color: #007020">application</span> <span style="background-color: #fff0f0">"Preview"</span> <span style="color: #008800; font-weight: bold">to</span> <span style="color: #008800; font-weight: bold">set</span> <span style="color: #996633">currentTabUrl</span> <span style="color: #008800; font-weight: bold">to</span> <span style="color: #0000CC">path</span> <span style="color: #008800; font-weight: bold">of</span> <span style="color: #007020">front</span> <span style="color: #0000CC">document</span>
<span style="color: #008800; font-weight: bold">else</span> <span style="color: #008800; font-weight: bold">if</span> (<span style="color: #996633">frontAppName</span> <span style="color: #333333">=</span> <span style="background-color: #fff0f0">"Finder"</span>) <span style="color: #008800; font-weight: bold">then</span>
    <span style="color: #008800; font-weight: bold">tell</span> <span style="color: #007020">application</span> <span style="background-color: #fff0f0">"Finder"</span>
        <span style="color: #008800; font-weight: bold">if</span> <span style="color: #007020">exists</span> <span style="color: #996633">Finder</span> <span style="color: #0000CC">window</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #008800; font-weight: bold">then</span>
            <span style="color: #008800; font-weight: bold">set</span> <span style="color: #996633">currentDir</span> <span style="color: #008800; font-weight: bold">to</span> <span style="color: #0000CC">target</span> <span style="color: #008800; font-weight: bold">of</span> <span style="color: #996633">Finder</span> <span style="color: #0000CC">window</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #008800; font-weight: bold">as</span> <span style="color: #996633">alias</span>
        <span style="color: #008800; font-weight: bold">else</span>
            <span style="color: #008800; font-weight: bold">set</span> <span style="color: #996633">currentDir</span> <span style="color: #008800; font-weight: bold">to</span> <span style="color: #996633">desktop</span> <span style="color: #008800; font-weight: bold">as</span> <span style="color: #996633">alias</span>
        <span style="color: #008800; font-weight: bold">end</span> <span style="color: #008800; font-weight: bold">if</span>
        <span style="color: #008800; font-weight: bold">set</span> <span style="color: #996633">currentTabUrl</span> <span style="color: #008800; font-weight: bold">to</span> <span style="color: #996633">POSIX</span> <span style="color: #0000CC">path</span> <span style="color: #008800; font-weight: bold">of</span> <span style="color: #996633">currentDir</span>
    <span style="color: #008800; font-weight: bold">end</span> <span style="color: #008800; font-weight: bold">tell</span>
<span style="color: #008800; font-weight: bold">end</span> <span style="color: #008800; font-weight: bold">if</span>
</pre></div>
<h1 id="conclusion">Conclusion</h1>
<p>That's all I got for now. I'm going to try to run a continuous log of my current laptop's "context", so I need file appending eventually. Until then, cya later!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/applescripts1/</guid>
      <pubDate>Wed, 05 Jan 2022 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Examples from the Taxonomy of Automation</title>
      <link>https://andykong.org/blog/taxonomyofautomation/</link>
      <description>Summary and thoughts on the convenience of interfaces</description>
      <content:encoded><![CDATA[<html><body><p>Hello y'all. I've been thinking about automation, specifically, intuitive interaction design's role in coddling people. </p>
<p>My lab is called the Future Interfaces Group. We abuse hardware and software to make predictive interfaces and new interactions between users and technology. Every project needs a user study to verify usability or reliability — does the user like the interaction? How does the device compare to other ones like it? We think about these things to make devices that hopefully feel a bit magical, like they can tell what you're going to do before you do them.</p>
<p>I've been thinking about the usefulness of such research, especially since it doesn't feel like anyone's life is particularly impacted by the research we put out. While it's nice that the products we use are smooth and not frustrating, I sometimes feel that nobody would die or hate their lives if we took it all away. Do these things really build up into useful parts of someone's life? Or is it always just cruft, extra features and bloatware that nobody asked for? </p>
<p>I'm getting off topic. Recently, I read a Tumblr blog called Crap Futures (I'm gonna abbreviate it as CF) which shared many of my thoughts about the apparent shittiness of the future we're creating. IoT devices everywhere that go down every week due to ransomware attacks or server outages does not sound like my idea of a good tech future. </p>
<p>They wrote one particular post called <a href="https://crapfutures.tumblr.com/post/180304398284/scratch-an-itch-a-taxonomy-of-automation">Scratch an itch: A taxonomy of automation</a> that really got me thinking about the degrees of automation in our lives. I'm going to summarize it here, but you should definitely give it a read too to see where I'm getting these ideas from. </p>
<h1 id="taxonomy-of-automation">Taxonomy of Automation</h1>
<p>I see automation's actors as the human and the device/tech/automator, and I am going to explain CF's taxonomy of automation in terms of the actions: who does the sensing, and who does the action. And I liked CF's concrete example of scratching an itch, so I'll continue with that as my main focus</p>
<p><br/></p>
<h2 id="level-1-human-sensing-human-doing">Level 1: Human Sensing, Human Doing</h2>
<p>On automation level 1, the human feels the itch and then reaches over to scratch it</p>
<h2 id="level-2-human-sensing-device-doing">Level 2: Human Sensing, Device Doing</h2>
<p>The human senses the itch, and uses a device to scratch it. This can be a stick or an ItchScratcher 3000.</p>
<h2 id="level-3-device-sensing-device-doing">Level 3: Device Sensing, Device Doing</h2>
<p>The device senses our itch (from imagery or something, use your imagination), then scratches it for us. </p>
<h2 id="level-4-device-prediction">Level 4: Device Prediction</h2>
<p>The device anticipates an itch, perhaps it occurs on a regular basis or shows a red spot before actually itching. The desire to scratch is circumvented entirely, through early anti-itch cream or pre-scratching. </p>
<h2 id="level-5-device-omniscience">Level 5: Device Omniscience</h2>
<p>The device anticipates and even pre-supposes an itch. It can do this to sell repairs of itself, or to sell its own usefulness. Complete loss of desire control. </p>
<p><br/></p>
<h1 id="examples">Examples</h1>
<p>I've mentioned this to a few friends, and each one has thought of a few examples of devices at each level that we already use today. As we'll see, nearly everything sits on level 1 and 2.</p>
<p>At level 1, we do all the work that we've always done by hand. Scratching, massaging, feeding ourselves.</p>
<p>Level 2 contains all "dumb" tools - the shovel, the spatula, the TV remote. We sense a boring channel coming on and click the remote to change it. Most of our technology sits at level 2, including our phones and the Roomba.</p>
<p>Level 3 largely drops off and contains almost nothing because level 3 features a loss of autonomy. Most devices stop short of that. Most of these require user confirmation, but I'd say that's pretty close to just letting the device do it, we just don't trust them enough. </p>
<ul>
<li>
<p>Our email client detects dates and offers to put them on a calendar, but it knows the limits of its own accuracy and doesn't create events on its own. </p>
</li>
<li>
<p>Auto-sharing wifi passwords and detecting lost devices are both features on this level. </p>
</li>
<li>
<p>GitHub Copilot also falls on this level, but it isn't quite good enough to be trusted.</p>
</li>
</ul>
<p>And nothing falls beyond that. It's kind of sad that so few things live at Level 3, even given all our crazy machine learning advances in the past twenty years. Our devices stay tools, useful when we use them and not otherwise. </p>
<p><br/></p>
<h1 id="crossapplication-to-people">Cross-application to people</h1>
<p>One thing that came up in my discussions is if anything sits past Level 4. I think our friends and family fall past level 3, since they're always looking out for us and can anticipate what we want pretty well (just think of your recent Christmas gifts!). And people besides them can sit anywhere on the spectrum. We can do a task ourselves (us sensing, us doing), tell someone what to do (us sensing, them doing), or tell someone what high-level thing to be working on (them sensing, them doing). Ideally they sit as high as possible on the levels. </p>
<p><br/></p>
<h1 id="conclusion">Conclusion</h1>
<p>I think we should be pushing robots as far up the automation hierarchy as possible. Each layer saves exponentially more time. The question is if the idea being automated is worth doing yourself, and if it is, why automate it? I close with a quote from the little prince</p>
<p class="caption">The Merchant passage from The Little Prince</p>
<p><img alt="The Merchant passage from The Little Prince" src="/static/tlpmerchant.png"/></p>
<p>A question for later: Why do we value human level 3 much more than robot level 3? (I think it's related to the fact that we know humans have opportunity cost but don't really think about computers having the same). What if we reported robot operating cost whenever they did a task for us? </p>
<p>Until then, cya around!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/taxonomyofautomation/</guid>
      <pubDate>Mon, 03 Jan 2022 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>How Good Are Stock Watchlists Anyway?</title>
      <link>https://andykong.org/blog/watchlists1/</link>
      <description>Fact-checking Timothy Sykes</description>
      <content:encoded><![CDATA[<html><body><p>Hello! Long time no see! In this post, I'm going to show y'all the behavior of stocks that were featured on a few of Timothy Sykes' watchlists earlier this year. </p>
<h2 id="background">Background</h2>
<p>I've been out for Winter Break for about 2 weeks now, and I've been thinking about a lot of stuff. I'm going to graduate from college after next semester (hopefully), and it's looking like I'll be attending grad school after taking a gap year. In my gap year, I'd like to learn how to make money without working for someone else (aside: if you have any tips for this please let me know). </p>
<p>One way I'm considering is some algorithmic stock market trading, or at least computer-assisted. While researching this topic online, I stumbled upon several different traders publishing "watchlists" of stocks that they were considering for their own portfolios. These are not guaranteed, but rather stocks that they note as "potentially interesting" for pattern trading. </p>
<p>Now personally, I think pattern trading is bull since all the graphs look the same to me. But I figured I should give these fellas the benefit of the doubt and at least try out their stock watchlists.</p>
<p><br/></p>
<h2 id="work">Work</h2>
<p>I signed up for <a href="https://alpaca.markets/">Alpaca Markets</a>, an algorithmic trading platform that also has a great Python API for real-time and historical stock data. I also browsed Mr. Sykes' old watchlists to get lists of stocks he had been promoting. Luckily, they had last-edited dates, so I could see the stock as it changed.</p>
<p>Using the API, I wrote a little scraper that takes a watchlist and startdate and shows the graphs of the stock prices after Mr. Sykes emailed them out to his eager audience. The results are disappointing. </p>
<p><br/></p>
<h3>1. Regular September Watchlist</h3>
<p>The graphs are all normalized price vs. time graphs, with the vertical red line indicating the date where Mr. Sykes shouted out this stock and the horizontal light blue line indicating a ratio of 1.0, or no price movement at all. </p>
<p>Each stock's history is normalized to the opening price of the stock on the day Mr. Sykes published his watchlist, to simulate a naive investor buying each stock as soon as it gets endorsed.</p>
<p><img alt="Regular September Watchlist Price Chart" src="/static/wl1_sept.png"/>
We see that his "Top Stocks to Watch Today: Thursday, September 16" watchlist did not perform so well, only 1 of the stocks above the breakeven mark after 2 months. </p>
<p>We also see the green and yellow spike that happened right before Mr. Sykes watchlisted this stock. While they were already spiking, his shoutout corresponded with even more spiking. </p>
<p><br/></p>
<h3>2. Weed Stocks September Watchlist</h3>
<p>If you think you're going to make money buying everything on this list, I wanna know what you're smoking and where you're getting it from. Only three stocks ever became profitable, and I'm not even listing the other 4 stocks from the canadian exchange.</p>
<p><img alt="Weed Stocks September Watchlist Price Chart" src="/static/wl1_weedsept.png"/></p>
<p><br/></p>
<h3>3. Regular December Watchlist</h3>
<p>Since only two of the stocks from this watchlist were in Alpaca's records, I made do. His watchlist was published about 3 weeks ago, and it seems that one has been doing very well!
<img alt="Regular December Watchlist Price Chart" src="/static/wl1_dec.png"/></p>
<p><br/></p>
<h2 id="open-vs-close">Open vs Close</h2>
<p>I looked at only three of Tim's watchlists, chosen by order in which I saw them. However, I wasn't sure whether to post the open or closing prices. Here are the weed stocks, one normalized to open and one to close.</p>
<p class="caption">Price normed to close</p>
<p><img alt="Price normed to close" src="/static/wl1_weedclose.png"/></p>
<p class="caption">Price normed to open</p>
<p><img alt="Price normed to open" src="/static/wl1_weedopen.png"/></p>
<p><br/></p>
<h1 id="conclusion">Conclusion</h1>
<p>No nilly-willy buys off some watchlist for me, get some insider trading if you can. And since all the stocks seem to go down, maybe Mr. Tim is just a great short salesman. </p>
<p>Cya next time!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/watchlists1/</guid>
      <pubDate>Sat, 25 Dec 2021 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>How Trees Fall Apart</title>
      <link>https://andykong.org/blog/howtreesfallapart/</link>
      <description>The differing decay patterns of trees during fall</description>
      <content:encoded><![CDATA[<html><body><p>You only get to experience 1 fall a year, and by the time you read this you're probably older than 15. This means you probably have only 65 falls left in you — remember to do your best to enjoy each one. </p>
<p>Anyway, this particular fall I took more of a look at the trees around me. I noticed that the green-yellow-red transition of leaves followed different patterns for different trees, and took photos of all the ones I could. Here follows the decay patterns of 4 different trees.</p>
<hr/>
<h1 id="1-top-down">1. Top down</h1>
<p class="caption">The decay here happens from the highest leaves to the lowest. </p>
<p><img alt="The decay here happens from the highest leaves to the lowest. My favorite tree this year" src="/static/topdown2.jpg"/></p>
<h1 id="2-inside-out">2. Inside out</h1>
<p class="caption">Core leaves brown completely while the outer ones are still green</p>
<p><img alt="Core leaves brown completely while the outer ones are still green" src="/static/insideout.jpg"/></p>
<h1 id="3-outside-in">3. Outside in</h1>
<p class="caption">My favorite tree of 2019 and 2020. The outermost leaves redden before the core</p>
<p><img alt="My favorite tree of 2019 and 2020. The outermost leaves redden before the core" src="/static/outside_in1.jpg"/></p>
<h1 id="4-all-at-once">4. All at once</h1>
<p class="caption">No regard for order! All leaves turn red at the same time, perfect example of communism</p>
<p><img alt="No regard for order! All leaves turn red at the same time, perfect example of communism" src="/static/allatonce.jpg"/></p>
<p>Evolutionarily, I'm not sure the decaying has a rhyme or reason. Maybe it makes sense to keep the outer ones green since they receive the most sunlight, but maybe it doesn't matter because red leaves can collect energy as well. Whatever the reason, it makes a beautiful sight. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/howtreesfallapart/</guid>
      <pubDate>Sat, 13 Nov 2021 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Real Time Fourier Transform Using Shaped Aperture</title>
      <link>https://andykong.org/blog/fourieraperture/</link>
      <description>Light speed!</description>
      <content:encoded><![CDATA[<html><body><p>Hello! I'm going to tell you something cool about light. Specifically, how to use apertures and lenses to perform the Fourier Transform of a 2D image at the speed of light.  </p>
<h1 id="backstory">Backstory</h1>
<p>When I was still a wee boy, my computational photography professor mentioned that a lens performs a Fourier Transform on light that enters it. A Fourier transform of light is a frequency domain representation of the image, and captures the exact same information as the regular picture. We don't usually notice this property of lenses because it only happens to coherent light, like a laser. One example of this is how a bright image (the sun) becomes a single point at the focus of the lens, because there is a 0-frequency component to the brightness entering the lens. </p>
<p class="caption">Example Fourier Transform of an image, specifically showing a text rotation application <a href="https://homepages.inf.ed.ac.uk/rbf/HIPR2/fourier.htm">(source)</a></p>
<p><img alt="Example Fourier Transform of an image, specifically showing a text rotation application" src="/static/fa_textrotation.png"/></p>
<p>Anyway, I noted this cool fact and carried on with my life</p>
<hr/>
<h1 id="1-year-ago">1 Year Ago</h1>
<p>About a year ago, I bought a coffee table book called "Laser Art and Optical Transforms", by a Mr. Thomas Kallard. The book contained a ton of photos of interesting laser effects that the author had created over his career as a lighting designer, and were catalogued down to the equipment he had used to make them. </p>
<p class="caption">A copy of the book, mine is black</p>
<p><img alt="A copy of the book, mine is black" src="/static/fa_kallardbook.png"/></p>
<p>I read the whole book (looked at pictures), and noticed that the back half of the book was entirely these aperture pictures. </p>
<p>To make these aperture photos, Kallard took a laser beam that was spread out by a filter, then shot through a sheet of paper with a shape cut out. The resulting image on the far wall did not resemble the hole at all, due to the self-interference of the laser light shooting through the paper cut-out. If the light source were an incoherent light source like a lamp or projector, this resulting image would look very different, but we don't have to worry about that since Mr. Kallard did use a laser.</p>
<p class="caption">Mr. Kallard's setup</p>
<p><img alt="Mr. Kallard's setup" src="/static/fa_kallardapparatus.jpg"/></p>
<p>Here's an example from the book, showing how changing the laser aperture shape changes the resulting image. The transform is slightly intuitive, but this reasoning breaks down with more complex apertures. </p>
<p class="caption">Two dots vertically stacked make a circle with horizontal lines, two dots stacked top right and bottom left create a circle with diagonal lines.</p>
<p><img alt="Two dots vertically stacked make a circle with horizontal lines, two dots stacked top right and bottom left create a circle with diagonal lines." src="/static/fa_kallardexample.png"/></p>
<hr/>
<h1 id="last-week">Last Week</h1>
<p>I showed a friend my book, and he wondered aloud how the output images were being formed by the light's interference. Using what I learned from computational photography, I assumed that this was a similar principle, and that changing the aperture and changing the "image" coming into a lens were nearly the same thing. This would mean that the aperture pictures that Kallard had captured were probably just the Fourier Transforms of the apertures he was using. </p>
<p>I had never tried it myself, but in that moment I remembered this web-based Fourier Transform <a href="https://homepages.inf.ed.ac.uk/rbf/HIPR2/fourier.htm">demo</a> I had found that let you do your own images. In a flash, we had taken a few photos of the apertures and fed them into the web demo. Lo and behold, what should we find but near-matches to the actual output Kallard had recorded!</p>
<h2 id="1-circles">1. Circles</h2>
<p>Here's some overlapping circles. The left image is the shape of the laser aperture, and the top-right image is the output that Kallard photographed. You can see the X and the overlapping arcs that face opposite directions on both, but the scale of the image is the only thing that changes.</p>
<p class="caption">Circle aperture + FFT</p>
<p><img alt="Circle aperture + FFT" src="/static/fa_circles.png"/></p>
<h2 id="2-spiraly-thing">2. Spiral-y thing</h2>
<p>Similarly here, it's clear that the aperture on the left creates features like the 4 bright quadrant and the surrounding "fan" shapes in both the web and real world version, though they are a bit harder to see.</p>
<p class="caption">Spiral aperture + FFT</p>
<p><img alt="Spiral aperture + FFT" src="/static/fa_spiral.png"/></p>
<hr/>
<p>Kinda crazy! I'm surprised that taking the FFT of a photo worked so well, considering that the photo has all sorts of distortion and noise from the paper warp and real world effects.</p>
<p>I'm still not sure why the scale of the image was so zoomed for Kallard's images. The spread of the light depends on your angle, and Kallard had quite a large distance between his lens and the photographing wall. Perhaps if you were to recreate this using a closer camera, you'd get the same thing we got.</p>
<p>Either way, pretty dope. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/fourieraperture/</guid>
      <pubDate>Mon, 20 Sep 2021 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Measuring 150 Amps With A DIY Shunt</title>
      <link>https://andykong.org/blog/highcurrentsource/</link>
      <description>Speccing a 150A@5V power supply, the NJE MK750</description>
      <content:encoded><![CDATA[<html><body><p>Hello everyone! Today I'm going to show y'all my testing of a high current, low voltage DC power supply.</p>
<p>Typical bench power supplies will go up to about 30V, and output 3A at most. For more powerful power supplies, their focus will usually be on voltage, going up to kilovolts at only a few amps or milliamps. </p>
<p>I recently had the need for the opposite kind of power supply, a high current and low voltage one, up to 200A at around 3V. I found this one on Ebay which seemed the fit the bill. Let me tell you all about it!</p>
<p class="caption">What could it be?</p>
<p><img alt="What could it be?" src="/static/mk750_backonly.jpg"/></p>
<h1 id="how-do-you-make-200a">How do you make 200A?</h1>
<p>Before I bought this, I had juggled a few alternatives. There are many sketchy ways you can generate huge currents. A few I considered were: </p>
<ul>
<li>
<p>A) shorting out around 10 drone/RC car LiPos in parallel (burst numbers are higher, but about 20A sustained current per battery)</p>
</li>
<li>
<p>B) Rectifying and rewinding a microwave transformer to output 3V from the wall</p>
</li>
<li>
<p>C) Harvesting RAM power supplies from dead motherboards (Sketchy YouTube videos put this at 20A, 3V)</p>
</li>
<li>
<p>D) Buying a battery-tester power supply, which are at weird voltages (8V, 5V, 4V, 2V, etc.) but super high currents.</p>
</li>
</ul>
<p>Option D beat out the others in terms of reliability, but those niche power supplies are quite expensive, even on eBay. Luckily, I found an older one that seemed to do exactly what I wanted. </p>
<p class="MK750 eBay page. I may have bought the last one"></p>
<p><img alt="MK750 eBay page. I may have bought the last one" src="/static/mk750_ebaypage.png"/></p>
<p>The NJE Corp. MK750, MK1000, and MK1500 were power supplies at their eponymous wattage, which output anything from 2V to 48V at massive currents. The only datasheet I could find was <a href="/static/mk750_datasheet.pdf" target="_blank">this one</a>, and there's not a ton of information on there (like what those 6 pins are for). But I needed this power supply, so I went ahead and ordered it. </p>
<h1 id="voltage-testing">Voltage Testing</h1>
<p>The first thing I checked was the voltage. After wiring the AC, ACC, and GND pins (3 prong power), I plugged it into a wall meter and checked the output. It looked good, almost perfectly 5V. With no load, the power supply drew around 70W, or 0.5A from the wall. </p>
<p class="caption">MK750 outputting 5V, as it should</p>
<p><img alt="MK750 outputting 5V, as it should" src="/static/mk750_5vconfirmed.jpg"/></p>
<h1 id="current-testing">Current Testing</h1>
<p>What I <em>really</em> wanted to know was if the MK750 could output the massive currents its manual had promised. This was a bit trickier. I could have used a clamp, but I didn't think of that. Instead, I watched an ElectroBOOM <a href="https://www.youtube.com/watch?v=j4u8fl31sgQ">video</a> about DIY current shunts and decided to use one of those.</p>
<p>Really, a DIY current shunt is just a wire. At 5V, to output 150A you need a load of </p>
<p>$$ 5V/150A = 0.033Ω = 33mΩ $$</p>
<p>This is usually the resistance of a few feet of wire. Mehdi talks about how multimeters suck at measuring low resistances, but they are good at voltage. I cut a random length of a random wire off the wall, and passed 1A constant-current through it to find the resistance. The measured mV corresponded directly to the mΩ of the wire. </p>
<p class="Thin wire of a good resistance, but not nearly enough current capacity"></p>
<p><img alt="Thin wire of a good resistance, but not nearly enough current capacity" src="/static/mk750_thinwire.jpg"/></p>
<p>This bit of wire happened to be around 48mΩ, which was nearly perfect. However, just before I plugged it in, I realized that the entire output 750W of the MK750 would pass through this tiny 18 AWG  wire and vaporize it instantly. Immediately I threw the thin wire away, and went foraging for some thicker power wires. </p>
<p class="Thicker wire of a perfect resistance"></p>
<p><img alt="Thicker wire of a perfect resistance" src="/static/mk750_thickwire.jpg"/></p>
<p>We cleaned the club a few days ago, and had thrown out some chunky orange power cables. Each cable had 3 conductors, so I took a length of this and crimped alternating ends together to make a triply long cable in the length of one. I did the same test as before, passing 1A through the combined cable. This resistance turned out to be actually perfect, at 34mΩ. </p>
<h1 id="using-the-3wire-shunt-34mω">Using the 3-wire shunt (34mΩ)</h1>
<p class="3-strand shunt resistor all set up, multimeter right next to it"></p>
<p><img alt="3-strand shunt resistor all set up, multimeter right next to it" src="/static/mk750_threestrandbefore.jpg"/></p>
<p>I connected our shunt to the power supply, then clipped my multimeter to the output. I'm measuring voltage to get the current. Since Ohm's law always holds, if we already calculated the resistance and then measure the voltage, we have everything we need. Time to plug it in!</p>
<p class="3-strand shunt resistor under test. Multimeter reads 5.14V"></p>
<p><img alt="3-strand shunt resistor under test. Multimeter reads 5.14V" src="/static/mk750_threestrandduring.jpg"/></p>
<p>And the MK750 delivers! Across our 35mΩ wire we see a drop of 5.14V, which works out to
$$\frac{5.14V}{0.035Ω} = 147.7 \text{Amps}$$
Woohoo! Almost exactly 150 amps at 5V. The readout from the wall meter said around 900W, so approximately the baseline 50W + 150Ax5V. Not bad for efficiency.</p>
<p>Oh, and the wires got HOT. I only left it on for a few seconds for safety, but even then the thermometer said the wire got over 100F. Crazy!</p>
<p class="Our shunt temperature after only 10 seconds of 150A"></p>
<p><img alt="Our shunt temperature after only 10 seconds of 150A" src="/static/mk750_hotwire.jpg"/></p>
<h1 id="using-a-2wire-shunt-23mω">Using a 2-wire shunt (23mΩ)</h1>
<p>I wanted to see what this power supply was really capable of. I had avoided using a shunt lower than 33mΩ because I was afraid that shorting out the power supply would break it. Thinking further, I realized that 33mΩ is already considered a dead short in any other application, so it probably wasn't a concern. </p>
<p>In our 3-stranded shunt, each wire was around 11mΩ. I moved the crimps so that it only used two wires, for a new shunt resistance of around 23.2mΩ. I reconnected everything to this smaller shunt, and switched it on. </p>
<p class="A snapshot of the rising voltage of our 2-strand shunt"></p>
<p><img alt="A snapshot of the rising voltage of our 2-strand shunt" src="/static/mk750_twostrandduring.jpg"/></p>
<p>As expected, the output voltage dropped a bit to compensate for the short. However, the voltage reading on my multimeter immediately started shooting up, beginning at 4.3V and climbing to 4.9V in the space of a few seconds. I assume this is from heat increasing the resistance of the wire, and shut it off quickly. </p>
<p>Using the initial reading of 4.3V, Ohm's law says the MK750 output 185A at first, and a clamp meter agreed at around 170A. The wall meter read 1120W during this test.</p>
<video controls="" src="/static/mk750_vid3.mov"></video>
<h1 id="closing-thoughts">Closing thoughts</h1>
<p>This power supply is amazing. Unfortunately, the NJE Corporation just filed for bankruptcy last year (2020), so I don't think we'll be seeing many more power supplies where that came from. </p>
<p>It also smells weird, but that's completely fine given its the excellent performance otherwise. </p>
<p>Cya next time!</p>
<h1 id="may-8-2024-update">May 8, 2024 Update</h1>
<p>A reader emailed me asking for the pinout of the auxiliary J1 connector on the front panel of the power supply. I never had use for these features so I did not know, but he was dedicated enough to order a manual and kind enough to send me a picture:</p>
<p class="caption"> The pinout of the J1 connector on the NJE MK750/MK1500 high current power supply </p>
<p><img alt="The pinout of the J1 connector on the NJE MK750/MK1500 high current power supply" class="addpic" src="/static/NJE_J1_PINOUT.jpg"/></p>
<p>These pins correspond to the functions mentioned in the 1-pager manual I have above.</p>
<p><img alt="" class="addpic" src="/static/nje_functions.png"/></p>
<p>And the sense pins are supposed to be connected to the outputs like so (I think to monitor the output voltage).</p>
<p class="caption"> From random eBay listing </p>
<p><img alt="From random eBay listing" class="addpic" src="/static/nje_senseplugs.png"/></p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/highcurrentsource/</guid>
      <pubDate>Mon, 06 Sep 2021 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Fast-charging capacitors</title>
      <link>https://andykong.org/blog/superchargingcaps/</link>
      <description>Who has time for 5RC?</description>
      <content:encoded><![CDATA[<html><body><p>I recently thought about a way to charge capacitors really quickly. </p>
<p>If you think about it, capacitors are sort of like buckets that you can fill with electrons. Their peak level is their voltage rating, and if you keep filling them they explode. Usually this means you only charge capacitors using a voltage less than or equal to their max voltage, to avoid the explosion.</p>
<p>But! This method sucks, and it's slow. As your cap fills up, the voltage difference between your source and the capacitor goes down, which makes your charging current go down too. Diminishing returns means that really big capacitors can take several seconds to charge up to a desired voltage. </p>
<h1 id="better-method">Better method</h1>
<p>Ideally, we'd use some massive voltage source for charging, and then turn it off immediately when our capacitor hits the final voltage. We only use the previous method because we humans have slow reaction times, and can't turn off the voltage source quickly enough to avoid a potential capacitor explosion.</p>
<p>But I'm <em>pretty sure</em> we've invented faster voltage switches than the human hand.</p>
<h1 id="enter-the-humble-microcontroller">Enter, the humble microcontroller</h1>
<p><img alt="Arduino-enabled fast off switch" src="/static/sc1_setup.jpg"/></p>
<p>All I did was connect an Arduino-controlled MOSFET to a 2200µF capacitor. The Arduino reads the voltage of the capacitor, and shuts off the MOSFET when the voltage gets past our set point (4V). I used 5V, 10V, 20V, and 30V for charging. Here's how long each one took:</p>
<p class="caption">Charging with 5V</p>
<p><img alt="Charging with 5V" src="/static/sc1_chargeto4with5.jpg"/></p>
<p class="caption">Charging with 10V</p>
<p><img alt="Charging with 10V" src="/static/sc1_chargeto4with10.jpg"/></p>
<p class="caption">Charging with 20V</p>
<p><img alt="Charging with 20V" src="/static/sc1_chargeto4with20.jpg"/></p>
<p class="caption">Charging with 30V</p>
<p><img alt="Charging with 30V" src="/static/sc1_chargeto4with30.jpg"/></p>
<p>In text form, charging a 2200µF capacitor to 4V took:</p>
<ul>
<li>
<p>5V -&gt; 5.6ms</p>
</li>
<li>
<p>10V -&gt; 1.7ms</p>
</li>
<li>
<p>20V -&gt; 0.76ms</p>
</li>
<li>
<p><strong>30V -&gt; 0.43ms</strong></p>
</li>
</ul>
<p>My power supply caps out at 30V, and that gives you a <strong>13x speedup</strong> in capacitor charging time!</p>
<h1 id="theoretical-speedup">Theoretical speedup</h1>
<p>We've seen how the real world does it; how about comparing it to theory?</p>
<p>Here's the equation that governs how fast a capacitor charges. It calculates the cap's voltage given some time <em>t</em> connected to a voltage source Vs.</p>
<p class="caption">Capacitor voltage after some time t, charging with a voltage Vsource</p>
<p><img alt="Capacitor voltage after some time t, charging with a voltage Vsource" src="/static/sc1_chargeVeq.png"/></p>
<p>Solving this backwards for <em>t</em>, we have some terrible looking equation for how long it would take to charge a capacitor given a source voltage.</p>
<p><img alt="Above equation solved for time t" src="/static/sc1_chargeTeq.png"/></p>
<p>Plugging in our previous charging voltages and magically using R=1.5Ω, we get the following charge times:</p>
<ul>
<li>
<p>5V -&gt; 5.3ms</p>
</li>
<li>
<p>10V -&gt; 1.7ms</p>
</li>
<li>
<p>20V -&gt; 0.73ms</p>
</li>
<li>
<p><strong>30V -&gt; 0.47ms</strong></p>
</li>
</ul>
<p>Greater than 10x theoretical speedup at 30V! And pretty good agreement with the numbers I measured. </p>
<h1 id="theoretical-mismatch">Theoretical mismatch</h1>
<p>Some of my times were faster than the theoretical times. What could cause these faster-than-theoretical real world charging times? </p>
<ul>
<li>
<p>Volt drop across the MOSFET? The MOSFET is fairly low resistance (IRFZ44NPbF claims 0.0175Ω at max), so that can't be it. </p>
</li>
<li>
<p>Lower R_cap than 1.5Ω? Possibly, but I'd just be guessing.</p>
</li>
<li>
<p>Perhaps the Arduino got a bit overzealous and cut off the capacitor before it truly charged to 4V? This seems like the case from some of the charging graphs &gt; 10V. For some reason, the reached voltage hit 4V, the Arduino shut off the MOSFET, and then the capacitor voltage dropped a bit. I'm not quite sure why this happened.</p>
</li>
</ul>
<h1 id="conclusions">Conclusions</h1>
<p>I used an Arduino for reading voltage, meaning that I only have an effective range of 0-5V. I can get a higher effective voltage range if I buffer and divide the capacitor voltage down before <code>analogRead</code>ing it. Maybe even a resistor divider would work.</p>
<p>The only way my program was able to run fast enough on the Arduino is because there was nothing in the loop except the voltage checking. With that, it was able to stop the capacitor under a few hundred us. With a faster µC, we could use even higher charge voltages and still be sure we'd turn it off in time.</p>
<p>A friend suggested I use voltage prediction to get a more precise turn-off time for the capacitor, sorta like an instant-read thermometer. I forgot about that until after I finished, but it's probably a good idea.</p>
<h1 id="head-fake">Head Fake</h1>
<p>This post is actually about supercapacitors. Why go through the trouble to find a high current 3V source when you could just use any old voltage source and turn it off in time? </p>
<p>Hell, for a large supercap, it'd charge slowly enough that you could probably disconnect it yourself. Just make sure you don't exceed your source or capacitor's current limit. I AM NOT RESPONSIBLE FOR BURNT CAPACITORS / FINGERS / HOUSES. </p>
<p>Be safe. Cya around!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/superchargingcaps/</guid>
      <pubDate>Mon, 09 Aug 2021 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Everything I Know About PEDOT:PSS</title>
      <link>https://andykong.org/blog/PEDOT/</link>
      <description>For now...</description>
      <content:encoded><![CDATA[<html><body><p>I've been reading about PEDOT, which is a conductive polymer. </p>
<p>I first heard about its use in making flexible, tissue-conforming electrodes for EMG and EEG, but it is apparently good for making electro-osmotic devices as well (as well as a whole host of other things).</p>
<h2 id="conductivity">Conductivity</h2>
<p>The conductivity is quite good relative to other organic polymers, with several papers reaching a sheet resistance of 20Ω/sq <a href="https://sci-hub.st/10.1016/j.orgel.2019.105451">[1]</a><a href="https://sci-hub.st/10.1002/elps.201000617">[2]</a>. Compare this to a 1oz sheet of copper's sheet resistance of 0.5Ω/sq, and it's about the resistivity of titanium or lead. Raw PEDOT:PSS is something like 100kΩ/sq, so the dopants are really necessary.</p>
<h2 id="usual-forms">Usual forms</h2>
<p>Usually aqeous solutions are around 1-4% PEDOT:PSS by weight, with some adding 5% diethylene glycol to increase the conductivity. The PSS is needed for enhanced solubility, though it negatively impacts the conductivity. The reference [1] above adds a graphene oxide solution as well, also to increase conductivity. It's sometimes confusing because they give the conductivity instead of the resistance, but I'm sure it's convertible.</p>
<p>Sigma-Aldrich sells it in dried pellets as well. Some guy on the cyan site said dissolving it in water will destroy its conductivity, but that's what <a href="https://www.nature.com/articles/srep17045#Sec4">this paper</a> did with no problem. About a gram of this stuff costs $50, and people on ebay are no kinder ($50 for 30g of 1.1%)</p>
<h2 id="working-with-it">Working with it</h2>
<p>Sheets of this stuff are made by pouring out a solution and letting it dry. Some people will fire it in an oven at 60C for a few hours to get all the water out. In the end, you get a flexible sheet of it. Kinda cool!</p>
<p>You can also remove the PSS after drying to increase conductivity. Dipping it in a solvent does the trick <a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC8071320/">here</a></p>
<p>You can add PDMS to make it stretchier (up to 80%!), but you'll need to add this surfactant with the dope name Triton X-100. <a href="https://sci-hub.st/10.1016/j.orgel.2019.105451">Reference</a></p>
<h2 id="what-i-want-to-do-with-it">What I want to do with it</h2>
<ol>
<li>I want to deposit some and do a sheet resistance test, maybe with diethylene glycol or ethylene glycol</li>
<li>Since ethylene glycol does approximately the same thing as the much more poisonous diethylene glycol, maybe do a test with that instead <a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC8071320/">[paper]</a></li>
<li>Maybe since propylene glycol is even less poisonous than that, use it instead? It seems to work for conductvity if you soak PEDOT in it after drying it. </li>
</ol>
<p class="caption">Ethylene Glycol &gt; Diethylene Glycol</p>
<p><img alt="Ethylene Glycol &gt; Diethylene Glycol" src="/static/pedot_EGoverDEG.png"/></p>
<h2 id="conclusions">Conclusions</h2>
<p>Ok so it's not much, but I hope it gives you some good leads. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/PEDOT/</guid>
      <pubDate>Thu, 05 Aug 2021 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Sugar-Metering My First Watermelon</title>
      <link>https://andykong.org/blog/firstwatermelon/</link>
      <description>Tastes like Capri-sun! Bonus: Lychee and Longan</description>
      <content:encoded><![CDATA[<html><body><p>I recently <a href="../refractometertesting">bought and tested</a> a refractometer's ability to determine the sugar content of various drinks to gain a better awareness of the stuff I eat. As promised, I have returned with more stats on fruit!</p>
<h1 id="watermelon-sugar--high">Watermelon Sugar - high!</h1>
<p>I bought a small watermelon and cut it up into bowls, which are by far the most fun way to eat a watermelon. I ate about half the bowl, then separately juiced up part of watermelon flesh in the very center and some near the wall (still red, but right beside the white flesh). </p>
<p>The accumulated juice from each part of the watermelon was collected and measured visually in my refractometer. Since each watermelon gives two bowls, I tested both regions in both bowls to have more precise numbers. Here's the refractometer pics:</p>
<p class="caption">Watermelon center and edge sugar concentrations</p>
<p><img alt="Watermelon center and edge sugar concentrations" src="/static/wm1_watemelonrefract.jpg"/></p>
<p>The juice near the center of the watermelon was around 7.9% and 8.1%, which is about the same as the Capri-sun I measured last time. The juice near the edges was 6.4% and 5.8%, so a few percentage points lower than the center. </p>
<h1 id="bonus-lychee-and-longan">Bonus, Lychee and Longan</h1>
<p>I also had some Asian fruit on-hand, the juicy lychee and longan. I could taste that the Longan were a bit sweeter, but I wasn't sure how much. </p>
<p class="caption">Tasty Lychee fruit</p>
<p><img alt="Tasty Lychee fruit" src="/static/wm1_lycheepic.jpg"/></p>
<p>They turned out incredibly high, with the longan juice twice as sweet as Coke!</p>
<p class="caption">Lychee refractometer photo, 18.8% sugar</p>
<p><img alt="Lychee refractometer photo, 18.8% sugar" src="/static/wm1_lycheesugar.jpg"/></p>
<p class="caption">Longan refractometer photo, 20.8% sugar</p>
<p><img alt="Longan refractometer photo, 20.8% sugar" src="/static/wm1_longan.jpg"/></p>
<p>The Lychee are 18.8% sugar, and the Longan are 20.8%! Off by 2%, but on a whole different level. I'm sure I'm getting extra nutrients with these fruits, but that's a lot of sugar for a plant! How'd they figure out how to do that? Was it selective breeding? Incredible. We're lucky the Americans haven't found out about these <em>en masse</em>!</p>
<h2 id="brief-discussion">Brief Discussion</h2>
<p>This watermelon tasted regular sweet to me, maybe a bit on the light side. IMO, a good watermelon would taste like the center of this one, but throughout the whole fruit. I'm just spoiled by my ability to pick good watermelon. </p>
<p>It's interesting that such a sugar concentration gradient (6% and 8%) can exist in a medium that is mostly water; maybe over time the concentration stabilizes from center to edge? Does the watermelon get sweeter too, as it ripens further? </p>
<p>I forgot to pat the melon before cutting it up, so I get no data points about how the sound correlates to the watermelon's sweetness. I'll have to buy another and record it.</p>
<p>Amazing sugar levels in Longan and Lychee though. They are sure worth the price! I'm sure the actual sugar percentage including fruit solids is lower, but the juice is super high in sugar.</p>
<p>Cya with my next watermelon!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/firstwatermelon/</guid>
      <pubDate>Tue, 27 Jul 2021 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>How to get access to your Fitbit data (API tutorial)</title>
      <link>https://andykong.org/blog/fitbit1/</link>
      <description>Navigating a slightly complicated process for API noobs</description>
      <content:encoded><![CDATA[<html><body><p>I bought a Fitbit to access my heartrate live, or at least to track the way it changes over time. After wearing it for about two weeks, I figured I had enough data to begin requesting it through their API. However, I got confused because there's a bunch of different API names and webpages. I figured it out, and here's how you do the same.</p>
<p>My end goal is a command line GET request that dumps my heartrate logs, for use in Python scripts or a website. To get there, we will use the Web API.</p>
<h1 id="1-sign-in-and-register-your-app-here">1. Sign in and register your app <a href="https://dev.fitbit.com/apps/new">here</a></h1>
<p>Any URL will do for every URL they ask you for. For every URL I just used my personal website (the one you're sitting on now!). This is meant for external users, so when they agree to let you access their data you can get their authorization token. However, I'm just setting it up once, so I'm ok with manually snipping out my keys and such from the URL redirect. </p>
<p>I also selected "Personal" while registering my app, so I can get higher fidelity heartrate data.</p>
<p class="caption">Register a Fitbit app page</p>
<p><img alt="Register a Fitbit app page" src="/static/fitbit1_register.png"/></p>
<h1 id="2-head-to-manage-my-apps-and-click-on-oath-20-tutorial-page-link-here">2. Head to "Manage My Apps" and click on "Oath 2.0 tutorial page" <a href="https://dev.fitbit.com/apps/oauthinteractivetutorial">[link here]</a></h1>
<p>I left all the settings default and clicked the link under step 1. Accept the agreement, and you will be redirected to the page you filled in earlier, with a bunch of params after a "#". Copy everything past the "#" and paste it in the "2. Parse response"</p>
<p>After pasting, the site should auto-generate you an "API endpoint URL: <code>https://api.fitbit.com/1/user/-/profile.json</code>" as a <code>curl</code> command. It redirects to your own profile though. To ask for more useful information, you can check out the Web API reference <a href="https://dev.fitbit.com/build/reference/web-api/heart-rate/">here</a>. For instance, to get heartrate, the API endpoint is <code>https://api.fitbit.com/1/user/-/activities/heart/date/today/1d.json</code>. Paste this in, and you should see the command change. </p>
<p>I'm not going to show you any commands I ran, that would give away my Auth token. Here's the results of the heartrate one though. Granularity goes down to 1 second, though I'm not sure why you would need that. </p>
<p class="caption">Successful heartrate output from the terminal</p>
<p><img alt="Successful heartrate output from the terminal" src="/static/fitbit1_heartrateout.png"/></p>
<p>This is a short post, but I wasn't sure what else needed to be done while I was navigating the process. Turns out it's just two things. Cya next time!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/fitbit1/</guid>
      <pubDate>Mon, 26 Jul 2021 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Comparing The Sugar Percentage Of Peach Juice vs. Sugary Drinks</title>
      <link>https://andykong.org/blog/refractometertesting/</link>
      <description>Testing out a sweet scientific instrument</description>
      <content:encoded><![CDATA[<html><body><p>It's a shame: I've met many smart people who were never educated in the fine art of slapping a watermelon to determine their ripeness by their (usually immigrant) parents. I thought I could do these poor folks a great service by using technology to help them figure out a watermelon's sweetness for them.</p>
<p class="caption">Refractometer on the left. The liquid goes on the blue part at the top. The bar chart on the right is what you see through the peephole. </p>
<p><img alt="Refractometer on the left. The liquid goes on the blue, tilted part. The bar chart on the right is what you see through the peephole. " src="/static/refractest_displaypic.png"/></p>
<p>The first step is determining how sweet the watermelon is, so of course I had to go buy a refractometer. As it turns out, adding sugar to water changes how much light rays bend when they go through the water, AKA the index of refraction (IOR). A refractometer lets anyone look through a peephole to compare an unknown sugary liquid to water (0% sugar). By looking at where the new line of intersection is on the graph, we can figure out exactly how many degrees Brix there are. Brix is just a complex way to say % sugar by weight. </p>
<p class="caption">Arrangement of all the liquids I had to try</p>
<p><img alt="Arrangement of all the liquids I had to try" src="/static/refractest_showcase.jpg"/></p>
<p>After I got the refractometer, I really wanted to make sure it gave out accurate numbers for both fruit and regular liquids. It just so happened that there were several sugary drinks in my pantry leftover from a club party, which listed their grams of sugar and grams of total mass under Nutrition Facts. Using this, I'd be able to determine the proper sugar content before confirming it with my refractometer. </p>
<hr/>
<h1 id="onto-the-results">Onto the results!!</h1>
<p>In all, I measured 3 drinks (Caprisun, Coke, Izze Green Apple), 1 fruit (peach), and 1 syrup (Hershey Caramel). I'll present them in increasing order by sugar content</p>
<h2 id="caprisun-79-brix">Caprisun (7.9˚ Brix)</h2>
<p>The refractometer hit this one on the head, which was helped by the fact that the Caprisun is so liquidy and has very few other ingredients</p>
<p>True sugar: Grams carbs / grams total = 14g / 177g = 7.9%<br/>
Refractometer: 7.9% </p>
<p class="caption">Caprisun, Pacific Cooler edition</p>
<p><img alt="Caprisun, Pacific Cooler edition" src="/static/refractest_caprisun.png"/></p>
<h2 id="izze-green-apple-97-brix">Izze Green Apple (9.7˚ Brix)</h2>
<p>A little further off on this drink. It might have been because of the carbonation forming bubbles in the sample.</p>
<p>True sugar: 24g / 248g = 9.7% <br/>
Refractometer: 9.4%</p>
<p class="caption">Izze Green Apple. Tastes like green apple.</p>
<p><img alt="Izze Green Apple. Tastes like green apple." src="/static/refractest_izze.png"/></p>
<h2 id="regular-coke-105-brix">Regular Coke (10.5˚ Brix)</h2>
<p>This number was a bit harder to find. Turns out a 12oz can of Coke is 355mL, but the density is a bit higher than that of water (1.042 g/mL), which affected the denominator. </p>
<p>The change in density may also be the reason the Izze's reading is off. Using the density of Coke at the volume of Izze gives a true sugar rating of 9.39%, which is exactly what we got from the refractometer.</p>
<p>True sugar: 39g / 369.9g = 10.5%<br/>
Refractometer: 10.3%</p>
<p class="caption">Coke had the highest amount of sugar by weight, but not by much (~2.6% higher than the lowest, Caprisun)</p>
<p><img alt="Coke had the highest amount of sugar by weight, but not by much (~2.6% higher than the lowest, Caprisun)" src="/static/refractest_coke.png"/></p>
<h2 id="peach-87-brix">Peach (8.7˚ Brix)</h2>
<p>I juiced a slice of this peach, but forgot to take a photo before beginning to eat the rest. No ground truth for this one. </p>
<p>Refractometer: 8.7%</p>
<p class="caption">So, still think fruit is healthy?</p>
<p><img alt="So, still think fruit is healthy?" src="/static/refractest_peach.png"/></p>
<h2 id="hershey-caramel-61-brix">Hershey Caramel (61˚ Brix)</h2>
<p>Because this liquid is so syrupy (it is syrup), I had to dilute it down. My refractometer range also stops at 38˚ Brix. I added 1 part caramel, 9 parts water and mixed it until uniform. I then added this liquid to the refractometer and read it. </p>
<p>True sugar: 25g / 40g = 62.5%!<br/>
Diluted 10x: 6.25%<br/>
Refractometer: 6.1%</p>
<p class="caption">Majority sugar caramel syrup</p>
<p><img alt="Majority sugar caramel syrup" src="/static/refractest_hershey.png"/></p>
<h1 id="closing-notes">Closing notes</h1>
<h2 id="refractometer-limitations">Refractometer Limitations</h2>
<p>I had a lot of fun running these experiments, partly because I got to sip sugarly liquids while doing it. I'm also shocked at how accurately my Amazon refractometer performed. However, there were a few challenges with using the refractometer that I want to mention.</p>
<ol>
<li>Sugar and salt are confounding; both raise the index of refraction at similar concentrations. This must be adjusted for in higher-salt liquids.</li>
<li>Carbonated drinks bubble up underneath the test plate, which can't be good for accuracy. </li>
<li>Limited range of 0-38% sugar means dilution is necessary for some solutions, which can add error.</li>
<li>Lack of density knowledge about Izze or Coke may have also skewed my numbers. As I mentioned above, adjusting the weight of Izze using a higher density gave an alternate sugar percentage that matched exactly what I read on my refractometer.</li>
<li>No precision or digital readout means measurements are slow and annoying to perform manually. </li>
</ol>
<h2 id="weird-findings">Weird findings</h2>
<p>The variance in the sugar % of drinks is surprisingly small. To be fair, I only did a small number of drinks, but they only varied by 2.6% despite tasting wildly different. </p>
<p>Also, Caprisun is healthier than peaches if we're going by sugar percentage. Tell that to your mom next time she berates you for picking an "unhealthy" snack!</p>
<p>Now, onto the humble watermelon!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/refractometertesting/</guid>
      <pubDate>Fri, 23 Jul 2021 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Paper Delivery: Sinusoidal Skin Displacements</title>
      <link>https://andykong.org/blog/sinepaper/</link>
      <description>Retro papers you probably missed, but shouldn't have!</description>
      <content:encoded><![CDATA[<html><body><p>I've been reading a ton of research about how our touch receptors work. You may think it's simple, and it's not. I'm gonna show y'all a seminal paper on how our nerves react to mechanical sensation. When they dropped this supa hot fire in 1982, it inspired a lot of research which underlies our current understanding of how our brain processes touch.</p>
<h1 id="theyre-made-of-meat">They're made of meat</h1>
<p>Quick recap: there's 4 mechanoreceptors in our skin, and they each detect something different; low and high frequency vibrations, pressure, and skin stretching. Each receptor is a special structure specially designed to release ions in response to a very narrow kind of stimulus. </p>
<p>Each receptor is attached to a nerve cell, and triggers action potentials in the nerve only when their stimulus occurs. The stronger the stimulus, the faster the receptor dumps ions onto the nerve, and the more often their action potentials get fired. </p>
<p>These nerves follow a direct path to the brain, and each nerve gets its own "column"-ish of brain cells that receive and process its impulses. So when someone touches your finger, they are firing neurons in on particular, consistent location on your brain's surface. This is how our brains know where we were touched. </p>
<p>But how do our brains know <em>what</em> we touched? How do our neurons interpret that chain of action potentials from the hand's receptors into the sensation of touching fabric, or holding a wooden block? </p>
<p class="caption">Is this a response from touching a cat, or a dog? Your brain knows, even if you might not!</p>
<p><img alt="Is this a response from touching a cat, or a dog? Your brain knows, even if you might not!" src="/static/sinu_singlenervefirings.png"/></p>
<h1 id="old-news">Old news</h1>
<p>Because the fire rate was the most obvious change in response to stronger stimulus, scientists used to think that the neurons talked to each other using average firing frequency. This is called a rate code. </p>
<p>However, this hypothesis is being challenged today. The brain can react to certain stimulus faster than it takes for a receptor to fire twice, for instance in reflexes. Since the neuron needs at least two firings to determine a "rate", researchers guess that there must be some other information present in a single firing spike besides just how many there are. </p>
<p>Though they don't really talk about neural coding at all, the paper <a href="/static/johansson1982sinu.pdf">"Responses of Mechanoreceptive Afferent Units in the Glabrous Skin of the Human Hand to Sinusoidal Skin Displacements"</a> offers a compelling reason to discard the rate code hypothesis. </p>
<h1 id="papers-setup">Paper's Setup</h1>
<p>Research papers always take a long time to get to the point. In this paper, they poked a plastic rod into a participant's skin, directly over one of the four mechanoreceptors. </p>
<p>They modulated the rod in a sinusoid, and kept halving its total movement length, starting at 1mm and stopping at 0.125mm peak-to-peak total travel. 1mm is considered 0dB, 0.5mm is -6dB, and so on.</p>
<p class="caption">Sinusoidal stimulus used to move the rod, and the action potentials produced</p>
<p><img alt="Sinusoidal stimulus used to move the rod, and the action potentials produced" src="/static/sinu_stimulus.png"/></p>
<p>The rod was moved for 5 periods of a sinusoid, at a bunch of different frequencies (powers of 2 from 0.5 to 256Hz, then also one at 400Hz). </p>
<p>How'd they find these mechanoreceptors anyway? They used something called microneurography, or "stab tiny needle into the nerve repeatedly until you only see one mechanoreceptor spiking when we poke the victim's hand with a stiff hair". Kinda cool that our body just works like that, and we can exploit it so well. </p>
<h1 id="graphs-please">Graphs please?</h1>
<p>Alright, alright! Here's the averaged responses for the PC, or Pacinian corpuscle. These are egg-shaped receptors that sense high frequency vibrations. The field typically uses 1 impulse/stimulus as a breakpoint to indicate the active range of a mechanoreceptor. For the PCs at full amplitude, they are all-sensitive.</p>
<p class="caption">Responses per sinusoidal period for all recorded Pacinians</p>
<p><img alt="Responses per sinusoidal period for all recorded Pacinians" src="/static/sinu_PCchart.png"/></p>
<p>This graph is what provides the really good reason against the rate coding hypothesis. Consider the 0dB line for 1Hz and the -6dB for 16Hz (marked in orange). The firing rates are nearly the same, yet we can clearly tell the difference between a 1Hz and 16Hz movement! </p>
<p>Clearly, there is a little more going on than simple rate coding. </p>
<h1 id="if-not-rate-what-else">If Not Rate, What Else?</h1>
<p>Current researchers are looking into using relative lag between first firing times from a local group or "population" of mechanoreceptors. Relative latency was used in 2008 in a salamander retina with great success:</p>
<blockquote class="twitter-tweet tw-align-center"><p dir="ltr" lang="en">In a 2008 paper, researchers measured a salamander's eye to see how retina cells encoded dark vs bright light when they talked to the brain. Instead of spike count or firing rate, ganglions changed their activation time relative to other ganglions near them. <a href="https://t.co/9mh86s6cwi">pic.twitter.com/9mh86s6cwi</a></p>— Andy (@oldestasian) <a href="https://twitter.com/oldestasian/status/1407171439359311874?ref_src=twsrc%5Etfw">June 22, 2021</a></blockquote>
<script async="" charset="utf-8" src="https://platform.twitter.com/widgets.js"></script>
<p>Other peeps are using the interspike period, or burst gap, or silent gap between series of nerve firings from a single nerve to determine the frequency of vibration or texture that's being touched.</p>
<p>I think it's some combination of latency across multiple populations, and maybe the number of spikes being sent. But what do I know? Just that there's a lot more to learn about one of our most foundational senses! Cya later!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/sinepaper/</guid>
      <pubDate>Tue, 22 Jun 2021 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Finding My Pacinian Corpuscles in vivo</title>
      <link>https://andykong.org/blog/mechanoreceptor2/</link>
      <description>If only Galen had had a vibrating motor</description>
      <content:encoded><![CDATA[<html><head><style type="text/css">
    p{
        margin-bottom: 1.5em;
    }
</style>
</head><body><p>Hello! I found out you could identify the exact location of one of your mechanoreceptors under the skin without needing a surgeon to cut you open, and I'm gonna show you how it's done. </p>
<p><br style="line-height: 1.5em;"/></p>
<h2 id="background-on-mechanoreceptors">Background on mechanoreceptors</h2>
<p>I have four unique mechanoreceptors embedded in my skin, and you probably do too. These are like little sensors in our skin which detect sensations like pain, hot and cold, vibration, and pressure.</p>
<p>I read in a psychology textbook that out of the four mechanoreceptors, the two that live in deeper tissue are much less plentiful than the ones that live in the shallow tissue. These deeper ones are the Ruffini endings, which gauge skin stretch near joints, and the Pacinian corpuscles, which detect higher frequency vibrations. They are so sparse, in fact, that you can individually locate them on your skin surface. </p>
<p class="caption">MRI scan of the Pacinian corpuscles in a corpse. <a href="https://www.semanticscholar.org/paper/Pacinian-corpuscles%3A-an-explanation-for-palmar-on-Rhodes-Murthy/dfc1c17d4cff70596b4bc3c913e5fef8ef69eeaa" target="_blank">Source</a> </p>
<p><img alt="MRI scan of the Pacinian corpuscles in a corpse " src="/static/mech2_pacinian.png"/></p>
<p>The Pacinian corpuscles are the focus of this post since their diameter is about 1mm, which makes them visible to the naked eye. I won't add the photo, but if you google it you'll find photos of a dissected hand with the Pacs exposed. They're huge compared to the other receptors!</p>
<p>I'm abbreviating the Pacinians as Pacs, cause I'm gonna wind up using the word a lot. </p>
<p>Anyway, My textbook claimed that the Pacs had a big receptive zone that they could sense vibration over, and that they had a peak sensitivity directly over the Pac itself. Looking at this MRI scan, I figured I'd try to find my hand's vibrational "hotspots" using a tiny vibrating wire. </p>
<p><br/></p>
<h1 id="hardware-setup">Hardware Setup</h1>
<p>I always keep some linear resonant actuators on me, and they came in handy for generating a vibration of a specific frequency. These are just small, self-contained solenoids. I taped two to a jumper wire, and this acted as my vibrating point. I chose a higher vibration frequency of 500Hz to avoid the receptive ranges of my other mechanoreceptors. </p>
<p class="caption">Pacinian Stimulator 3000</p>
<p><img alt="Pacinian Stimulator 3000" src="/static/mech2_pacstim.jpg"/></p>
<p>I then poked this point all over my hand for about 2 hours. The Pacs are "rapidly-adapting" mechanoreceptors, meaning that they only respond to new stimulus and not to sustained stimulation. This meant each time I poked my skin, I felt a vibration sensation which immediately went away within half a second. So I had to do it a lot. </p>
<p>I had some luck though. By repeating pokes over the same area a few times, I felt certain points that were especially sensitive. Not only would it feel like vibration, it would feel like a little spike or path of sensation travelling up my wrist/hand. I marked these points with a pen tip.</p>
<p class="caption">Dots of high vibration sensitivity on the back of my hand</p>
<p><img alt="Dots of high vibration sensitivity on the back of my hand" src="/static/mech2_pacback.jpg"/></p>
<p>Can I confirm that these were where my Pacs lived in my skin? No, I'm not going to actually cut myself and check. But the distribution lines up, and the sensation duration and localization also make sense to me. I don't think that the other mechanoreceptors would have been able to detect the 500Hz vibration frequency super well. </p>
<p>The palm was a bit harder to feel differences in sensation, since the skin thickness also varies from the pads to the joints. Here's a few spots I found on my palm:</p>
<p class="caption">Dots of high vibration sensitivity on my palm</p>
<p><img alt="Dots of high vibration sensitivity on my palm" src="/static/mech2_pacpalm.jpg"/></p>
<p><br/></p>
<h1 id="aftereffects">Aftereffects</h1>
<p>I held the spike with my right hand, and stimulated my left. I held it loosely, but still felt vibration in my right hand for a long time. At the end of the trial, my right hand had some weird sensations. For the next 30-45 minutes, whenever I tapped my index finger and thumb together, I felt a bouncing/springy sensation. Like hitting a drum, but the drum is just two of my fingers. It worked when I tapped other surfaces too, like my table. This felt strongest in my index finger, but it also worked for my middle finger and thumb. Really weird feeling, but probably not useful for anything. </p>
<p><br/></p>
<h1 id="closing-notes">Closing Notes</h1>
<p>Interestingly enough, there were a few spots on my back-hand that felt no vibration at all. Maybe a dead zone with no nearby Pacs? Blame my mom, I guess.</p>
<p>It was hard to ignore the feeling of the vibrating point poking me. I thought that the sharp-ish point coupled with vibration might actually damage the skin, and my skin did start hurting wherever I poked vertically. I found it hard to separate this sensation from the vibration I was looking for.</p>
<p>Cool effect! I have now separately stimulated the Pacs and the Merkel discs. Two down, two to go.</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/mechanoreceptor2/</guid>
      <pubDate>Wed, 16 Jun 2021 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Recreating the Traxion psychophysics effect</title>
      <link>https://andykong.org/blog/traxionreproduction/</link>
      <description>Only 8 years late!</description>
      <content:encoded><![CDATA[<html><body><p>Hello! I recently learned of this paper called <a href="https://lab.rekimoto.org/projects/traxion/">Traxion</a>, wherein a genius visionary named Rekimoto discovered that asymmetrical vibration with a linear vibration motor caused a "pulling sensation" on a user's finger. A fake force that feels 32 grams of real force! Amazing!</p>
<p>To tell the truth, I straight-up couldn't believe it until I tried it. With my limited setup, I produced 3V using an LM317 (powered by a 9V I acquired), then drove a transistor with an Arduino to generate the ramp/square wave I needed. </p>
<p class="caption">Traxion setup</p>
<p><img alt="Traxion setup" src="/static/traxion_setup.jpg"/></p>
<p>Inside the linear actuator, there's a little weight anchored by spring to the center position of the body. This weight is what moves forward when we give the device a HIGH voltage, and it jerks back when we leave it unpowered. This "asymmetry" is what creates the sensation of a force. </p>
<p>According to this more recent <a href="https://sci-hub.st/10.1109/HAPTICS.2016.7463151">paper</a>, the effect is caused by the skin dragging more in ON direction than when it turns OFF. This is due to the forward ON happening faster than the spring can pull it OFF. Something about detectable vs. unnoticeable acceleration. </p>
<p>The authors drive their device with a high plateau, followed by a slow ramp down. This eases the spring-back while keeping the ON switch waaay fast. They claim the effect is strongest at 40Hz, but I found that just a simple duty cycled square wave (100Hz, 20% duty cycle) does the effect too, just less. </p>
<p class="caption">Linear actuator I used. The little white pin is the weight, and it creates the force sensation in the length-wise axis</p>
<p><img alt="Linear actuator I used. The little white pin is the weight, and it creates the force sensation in the length-wise axis" src="/static/traxion_linearact.jpg"/></p>
<h2 id="note">Note</h2>
<p>This may sound silly, but I was surprised to feel vibration. Yea, yea, the thing is turning on and off really fast, but the paper only ever mentioned the force that it produced! I can't be blamed for wanting the vibration to go away to leave us with a free, fake force produced by a tiny vibrating motor. Maybe that's too easy though.</p>
<p>I want to see if this can be done on a larger scale, maybe from a handheld object? (VR controller would be cool). Though vibrating things are hard to hold onto. </p>
<p>That's all for now, cya later!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/traxionreproduction/</guid>
      <pubDate>Wed, 02 Jun 2021 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>MacGyvering Electronics From Merch</title>
      <link>https://andykong.org/blog/macgyver1/</link>
      <description>TFW no batteries :(</description>
      <content:encoded><![CDATA[<html><body><p>Hello! I moved into Chicago yesterday and needed to do some electronics. Specifically, I needed a battery. But I had left my LiPo bag at home, and my 9Vs with it! What to do?</p>
<h2 id="merch-to-the-rescue">Merch to the rescue!</h2>
<p>My friend had received a small, rechargeable ring light from a CMU thing. Since it had been made this year and not in 2008, I had figured it probably had a charging circuit and LiPo in it, which would be useful in the future. Today was that day!</p>
<p class="caption">Ring light on, courtesy of CMU</p>
<p><img alt="Ring light on" src="/static/mcg1_ringlight.jpg"/></p>
<p>Sam, if you're reading this, sorry for what you're about to witness. </p>
<p class="caption">Cute little LiPo powering the ring light</p>
<p><img alt="Cute little LiPo in the ring light" src="/static/mcg1_battery.jpg"/></p>
<p>So I pried off the back hinge and spring, then broke my thumbnail on the front panel. Tried again with my nail clipper while fixing the rest of my thumbnail, and this revealed what I knew had to be in there: the tiniest little LiPo I had ever seen. </p>
<p>Here's a better view of the circuit. I couldn't identify two of the chips here. </p>
<p class="caption">Cheapest way to charge the cheapest LiPo</p>
<p><img alt="Cheapest way to charge the cheapest LiPo" src="/static/mcg1_circuit.jpg"/></p>
<p>The top half is connected to the button which turns on the light. There's 3 levels of light + off, so maybe a 4-state... resistor? Another current limiting device? Not exactly sure. But the SOT-23 is a low-side MOSFET, and the battery's ground connection goes through the other chip before reaching the FET. </p>
<p>Bottom half is the battery charger. Looking at the resistors, it appears they're using a resistor divider with 5100Ω and 1200Ω to step the USB's 5V down to 5 x 5100/(1200+5100) = 4.04V, with at least 0.8mA going across the 1200Ω. Astounding cost saving measure. I'm not sure how the resistor is handling this, since almost 1mA across 1200Ω is 1W wasted heat, but props to them.</p>
<p>And of course, I didn't even have a soldering iron. Two alligator clips saved the day, scavenged from another project.</p>
<p class="caption">Alligator clips dangerously close to each other</p>
<p><img alt="Alligator clips dangerously close to each other" src="/static/mcg1_alligator.jpg"/></p>
<p>In the end, this worked for my purposes, except that it also let in current the other way. I had to add an LED to the path since I didn't have a real diode -.- but that's the last of my electronics adventure today.</p>
<h2 id="did-you-learn-anything">Did you learn anything?</h2>
<p>In the middle of doing this whole janky process, I went to CVS to buy scissors to cut some wire and didn't think to just buy a 9V. Serves me right for trying to be clever, next time I'm going for pragmatic.</p>
<p>Cya next time!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/macgyver1/</guid>
      <pubDate>Mon, 31 May 2021 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Stimulating the Merkel Discs: selective nerve stimulation with a TENS unit</title>
      <link>https://andykong.org/blog/mechanoreceptor1/</link>
      <description>The Firm Handshake-inator 3000</description>
      <content:encoded><![CDATA[<html><body><p>Hello! As you may know, there are 4 mechanoreceptors in the skin.</p>
<p>I wanted to create a specific sort of sensation on my skin, and I thought the best way would be to <strong>target one of these specific mechanoreceptors</strong>. </p>
<h2 id="background">Background</h2>
<p>These mechanoreceptors are like a biological equivalent of electronic sensors; cleverly shaped bits of nerve tissue that are sensitive to specific vibrations, sustained or brief pressure, etc, and report back to the brain what they feel. Through these sensors, you learn what textures feel like, what shapes you're touching, and all that cool tactile stuff we rely on every day. You can read more about them on <a href="https://en.wikipedia.org/wiki/Mechanoreceptor#Types">Wikipedia</a>. </p>
<p>While each receptor is most sensitive to a range of frequencies as stimulation (running your hand over a cloth for example), this tells me nothing about the way the receptors send this message back to the brain. </p>
<p class="caption">Pacinian corpuscle image I got from <a href="https://en.wikipedia.org/wiki/Pacinian_corpuscle">Wikipedia</a></p>
<p><img alt='Pacinian corpuscle image I got from &lt;a href="https://en.wikipedia.org/wiki/Pacinian_corpuscle"&gt;Wikipedia&lt;/a&gt;' src="/static/mr1_pacinian.png"/></p>
<p>For example, the Pacinian corpuscles are these bulb-like things which leak calcium ions when they are deformed. This leaked Ca causes nerves to fire at the base of the bulb, and the more deformation, the more Ca, and the faster the nerves can fire. Therefore this receptor bulb sends its info in a frequency-encoded fashion. But how fast? What electrical stimulation speed would I need to "pretend" to be a Pacinian corpuscle that's feeling a vibration?</p>
<p class="caption">Off-the-shelf TENS unit I used for this testing</p>
<p><img alt="Off-the-shelf TENS unit I used" src="/static/mr1_tens.jpg"/></p>
<p>Despite this limitation, I still found something cool. By placing a set of pads on the front of the bottom segment of my fingers, I was able to simulate a tight squeezing sensation on my finger. It felt as if my finger were being pressed on all sides by a vise.</p>
<p class="caption">One electrode on the back of my hand, one on the front bottom pad of a finger</p>
<p><img alt="One electrode on the back of my hand, one on the front bottom pad of a finger" src="/static/mr1_elecplacement.png"/></p>
<p>I'm using pulses of a few hundred microseconds, at a frequency of 70+ Hz. Lower than that, and the sensation no longer felt like squeezing. There's also a persistent buzzing feeling from the TENS, and it is on-par with the strength of the squeezing. A bit distracting, but the squeeze is cool. </p>
<p>I'm assuming the mechanoreceptors I'm stimulating are the Merkel discs, since squeezing is really a sustained pressure. You could argue I'm feeling a momentary pressure multiple times per second, but I think that's just the same thing as a sustained pressure. </p>
<h2 id="applications">Applications</h2>
<p>Ideally we get all the mechanoreceptors stimulating separately, since this would let us produce a whole world of different textures when we touch objects in VR. Side note, does anyone want to make a VR game together? We can call it Firm Handshake Simulator 2022.</p>
<p>We could also use it for finger-specific tasks like learning piano and guitar, or sign language. This could be used as a finger indicator. Besides that, I dunno.</p>
<p>Cya next time!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/mechanoreceptor1/</guid>
      <pubDate>Sun, 23 May 2021 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Body Channel Communication Tutorial Pt. 3</title>
      <link>https://andykong.org/blog/bcc3/</link>
      <description>Reinventing the radio and using touch as the antenna</description>
      <content:encoded><![CDATA[<html><body><div style="font-size:.75em;"> Previous posts here: <a href="../bcc1">[Part 1]</a> and <a href="../bcc2"> [Part 2] </a> </div>
<hr/>
<p>Hello! I have successfully implemented BCC, transmitting signals across the human body through touch alone. With the help of Sam, anything is possible. In this post I'll show you how it works, and some neat applications.</p>
<p><br/>
Here's a demo showing a reactive calendar. When a user touches the pad, their smart watch transmits their encoded ID to the computer. When decoded, the computer knows who is touching it, and can show that user's calendar. </p>
<p class="caption">Calendar that detects user identity through their touch</p>
<p><img alt="Calendar that detects user identity through their touch" src="/static/bcc3_caldemosmall.png"/></p>
<hr/>
<h1 id="recap-of-what-doesnt-work">Recap of what doesn't work</h1>
<p><a href="../bcc2">Last time</a> I showed that high frequency switching on a transmitter could be received as "spikes" of voltage on the receiver side. </p>
<p class="caption">Dense, thin groups of spikes are digital 1s, thick spikes of noise are the 60Hz leaking in to our 0 signal</p>
<p><img alt="Dense, thin groups of spikes are digital 1s, thick spikes of noise are the 60Hz leaking in to our 0 signal" src="/static/bcc2_every50msIS.png"/></p>
<p>It turns out I was wrong; the spikes are only visible because the grounds were galvanically linked. I had done the separation of receiver and transmitter using a USB isolator, which produces its own ground from the laptop's USB power. This ground happens to be produced at the same potential as the transmitter's ground, so they still share the reference. </p>
<p>When I powered the transmitter with a battery pack, the spikes dropped down to the level of the noise. They were still visible to the eye as higher movement bits of noise, but definitely impossible to detect computationally. Time to try something else.</p>
<hr/>
<h1 id="how-it-works">How it works</h1>
<p>I'm using a high frequency carrier wave to carry the bits, and an amp to detect the signal in the specific frequency band of the carrier wave. This is basically how radios work. The transmitter uses the digitally-modulated clock signal of the Arduino(8 MHz) to send 0s and 1s. The receiver is a passive RC high-pass filter, followed by an amplifier stage before the ADC.</p>
<h2 id="transmitter">Transmitter</h2>
<p class="caption">Picture of the transmitter</p>
<p><img alt="Picture of the transmitter" src="/static/bcc3_transmitter.jpg"/></p>
<p>I was lazy with the earlier prototype and tried to transmit bits using the output from flipping a pin in the <code>loop()</code> of the Arduino as fast as possible. This technique caps out at a frequency of around 100kHz, but I wanted to go higher. </p>
<p>I found a <a href="https://arduino.stackexchange.com/questions/16698/arduino-constant-clock-output">way</a> to output the Arduino's clock signal (16MHz square wave) on a pin. Connecting that to the input of a transistor, I could then turn the clock signal on and off using another pin on the base/gate. This became the transmitter signal: high frequency clock when on, grounded when off. </p>
<p class="caption">Raw signal from the transmitter</p>
<p><img alt="Raw signal from the transmitter" src="/static/bcc3_raw.jpg"/></p>
<p>This turned out to be even easier to implement than what I tried in Pt. 2, since I only had to turn on and off a digital pin in the middle loop.</p>
<p class="caption">Circuit for the transmitter, which is just a modulated clock signal</p>
<p><img alt="Circuit for the transmitter, which is just a modulated clock signal" src="/static/bcc3_transmittercircuit.jpg"/></p>
<h2 id="receiver">Receiver</h2>
<p class="caption">Picture of the receiver</p>
<p><img alt="Picture of the receiver" src="/static/bcc3_receiver.jpg"/>
The receiver side swamps with 60Hz when I touch the input pin. This is both because the human body is a big antenna for all EMI pickup, and because the Arduino's ADC draws current from the source to measure voltages. </p>
<p>To get rid of the 60Hz noise, we built an RC high-pass filter at 159Hz. It works surprisingly well for how close the cutoff frequency is to the noise frequency. After that, we had to add a lot of gain (300x) on the signal to see the square wave being modulated. We also built an artificial ground at 2.5V to act as the central reference for the op amp. </p>
<p class="caption">Circuit for the reciever, composed of the stages in the above paragraph</p>
<p><img alt="Circuit for the reciever, composed of the stages in the above paragraph" src="/static/bcc3_receivercircuit.jpg"/></p>
<h2 id="transmitted-signal">Transmitted signal</h2>
<p>Here's what we're sending. A header of 10 so the reciever knows a signal's coming, then an ASCII character. </p>
<p class="caption">Raw output from the transmitter with labeled sections of the data</p>
<p><img alt="Raw output from the transmitter with labeled sections of the data" src="/static/bcc3_transmittedraw.png"/></p>
<p>And on the receiving side, the signal appears perfectly among all the 60Hz noise. </p>
<p class="caption">Input on the receiving side, after filtering and gain. The bits are clearly visible</p>
<p><img alt="Input on the receiving side, after filtering and gain. The bits are clearly visible" src="/static/bcc3_receivedraw.png"/></p>
<h2 id="code">Code</h2>
<p>The repo can be found <a href="https://github.com/kongmunist/BCC">here</a>. The transmitter code is very simple, just a digital write and some array reading. The receiver is a little more complex, and a bit hacky to read bitstrings. Works surprisingly well though. </p>
<h2 id="final-demo">Final Demo</h2>
<p>Here's a video of the calendar demo. I didn't have time to rig up two "watches" with different IDs, but my friend who is uninstrumented is unable to access a calendar like I am. Sorry buddy, should've been a technlogist!</p>
<div style="text-align:center;">
<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/4pwZ0hJcawM" title="YouTube video player" width="560"></iframe></div>
<h1 id="final-notes">Final Notes</h1>
<h3>Limited signal propagation</h3>
<p>The signal does not spread across my entire body, which was a little weird. Wearing the watch on my left wrist, I can trigger the calendar using my left hand, left elbow, nose, and right elbow, but not my right hand. I think with more gain on my receiving side I'd be able to detect it there too. I think <a href="http://www.alansonsample.com/publications/docs/2018%20-%20UIST%20-%20Enabling%20Interactive%20Infrastructure%20with%20BCC.pdf">this paper</a> talks more about distance of effect.</p>
<h3>Low bitrate</h3>
<p>The Arduino ADC is fast enough to detect bits reliably at 1kHz, but not much higher than that. I'd need a dedicated ADC and some ring-buffer in software to get a higher bitrate. But honestly, I can't imagine the someone's ID being longer than a kilobyte or so, even if encrypted. that's a lot of bits!</p>
<h3>Cool factor</h3>
<p>It's dope that the hardware is so simple. One microcontroller, an op amp, and some passives, and your devices can detect not only touch but also WHO touched it. Very powerful, and quite simple to add to a smartwatch *hint hint wink wink*.</p>
<p>If you have questions about implementing this yourself, reach out via email or Twitter. BCC has some dope applications, and I'd love to create more demos using it. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/bcc3/</guid>
      <pubDate>Sat, 15 May 2021 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Outro to Intro to Woodworking</title>
      <link>https://andykong.org/blog/woodworkingday2/</link>
      <description>Crate, shelf, stool</description>
      <content:encoded><![CDATA[<html><body><p>Hello! I have finished up this class (and with it, the last semester of junior year ;(). In the past 3-4 weeks, I have learned how to use many other woodworking implements, some of which I could've figured out and some of which I couldn't've. The wood glue and nailgun were pretty intuitive, but the flush saw and forstner bits less so. </p>
<p>I have messed up a few times, and I'm putting these mistakes in public so you know what I won't mess up on again. </p>
<ul>
<li>
<p>Did a rip cut on the miter saw</p>
</li>
<li>
<p>Flush sawed past the peg and into my wood</p>
</li>
<li>
<p>Drilled 4 holes and then jigsawed to make a handle when I could have drilled two and it would've been way easier</p>
</li>
<li>
<p>Nailgun'd a crooked part twice before checking it</p>
</li>
<li>
<p>Nailgun'd nonperpendicularly so the nail stuck out of the wood piece</p>
</li>
</ul>
<hr/>
<h1 id="what-i-made-in-the-process">What I made in the process</h1>
<h2 id="bread-shelf">Bread Shelf</h2>
<p>This shelf was made so we could stop keeping bread on top of the fridge, where it blocked some cabinets. We had some preexisting screws in the wall which I measured, and then I made the decorative wall legs too short :(. I could only put one set of screws so the shelf is very weak. Holds bread good though. </p>
<p class="caption">Bread shelf without bread</p>
<p><img alt="Bread shelf without bread" src="/static/wood2_breadshelfwithoutbread.jpg"/></p>
<p class="caption">Bread shelf with bread</p>
<p><img alt="Bread shelf with bread" src="/static/wood2_breadshelfwithbread.jpg"/></p>
<h2 id="crate">Crate</h2>
<p>The crate was for an assignment. Very bulky, hard to store, but makes a good stool. </p>
<p class="caption">Crate assembled</p>
<p><img alt="Crate assembled" src="/static/wood2_crate.jpg"/></p>
<p class="caption">Crate holding Yerba Mate</p>
<p><img alt="Crate holding Yerba Mate" src="/static/wood2_crate2.jpg"/></p>
<h2 id="unfoldable-stool">Unfoldable Stool</h2>
<p>The camping stool was our final assignment. It’s meant to be foldable but I messed up the tolerances. Many stool jokes were made in the process. Anyway, seats one. </p>
<p class="caption">Pieces of the stool</p>
<p><img alt="Pieces of the stool" src="/static/wood2_stool1.jpg"/></p>
<p class="caption">Stool parts fit check</p>
<p><img alt="Stool parts fit check" src="/static/wood2_stool2.jpg"/></p>
<p class="caption">Completed seat!</p>
<p><img alt="Completed seat!" src="/static/wood2_stool3.jpg"/></p>
<h2 id="for-fun">For Fun</h2>
<p>Me and Elio also redesigned our club logo, and made it out of wood. Looks better on paper, but worth a shot.</p>
<p class="caption">Completed seat!</p>
<p><img alt="Completed seat!" src="/static/wood2_roboclublogo.jpg"/></p>
<p>Cya around next time I make stuff out of wood!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/woodworkingday2/</guid>
      <pubDate>Thu, 29 Apr 2021 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Body Channel Communication Tutorial Pt. 2</title>
      <link>https://andykong.org/blog/bcc2/</link>
      <description>Look Ma, no shared ground!</description>
      <content:encoded><![CDATA[<html><body><p>Continuing from <a href="../bcc1">last time</a>, I'm trying to make our body channel communication system work without requiring a shared ground between the two devices. And I got it working in a very bootstrapped way, which may be usable for our class demo. Read on for more!</p>
<hr/>
<h1 id="recap">Recap</h1>
<p>You may remember my two-Arduino setup from last time, using one as a transmitter and the other as a receiver. Ideally you'd want either uC to work for both functions, but that's easily tackled later. The previous setup utilized the shared ground of my computer's chassis to transmit signal reliably. I also showed some experiments I tried without shared ground, and they didn't work as well. The signals sent without shared ground had a lot of 60Hz noise, and the added signal square wave signal swamped the ADC's input range. </p>
<p class="caption">Shared ground BCC setup from last post</p>
<p><img alt="Shared ground BCC setup from last post" src="/static/bcc1_connectedground.jpg"/></p>
<hr/>
<h1 id="propagating-higher-frequency-signals-without-a-ground">Propagating higher frequency signals without a ground</h1>
<p>Since it's impossible to transmit a voltage with one wire (a potential difference inherently relies on having two "wires"), I decided to fake a radio transmitter. Because the capacitance of the body has decreased impedance for higher frequencies, signals can couple through the body better at higher frequencies. Last time, I used frequencies under 1kHz because I wanted to be able to see it on the serial plotter. No longer!</p>
<p>I wanted to change the transmitter frequency to something closer to 1MhZ. I started with <code>delayMicroseconds(1)</code> between direct digial port manipulation to switch the pin on and off, but got a longer delay than I expected when I measured it on an oscilloscope. Then I switched to using 8 No-ops between the digital switching, and got a faster square wave coming out. A half second timer turned the square wave on and off. The receiver code had no change, I left it analog sampling as fast as it could go.</p>
<p>Side note, I also tried writing the Arduino's 16MHz clock out to the transmitter pin since it's faster. It didn't work. When sending spikes with the clock, the analog readings on the receiver integrated together to a constant voltage, but with a large delay and rise time. Not fast enough!</p>
<hr/>
<h1 id="picking-up-the-spikes">Picking up the spikes</h1>
<p>When I first started testing, I realized that there was a great asymmetry depending on what powered the board. I used a USB isolator to "separate" the grounds, and initially I had the transmitter plugged into the isolated USB port. The signals I got had awful 60Hz noise, but when the train of square waves was transmitted you could still clearly see it. If I had stopped here, I would have to deal with a mediocre bitrate and a terrible filtering problem. </p>
<p class="caption">The little solid spikes are 60Hz noise, and the denser, thin spikes are periods of transmission.</p>
<p><img alt="Graph of serial plotter. The little solid spikes are 60Hz noise, and the denser, thin spikes are periods of transmission. " src="/static/bcc2_every50msIS.png"/></p>
<p>Then, I had the thought to switch them. Now the receiver was isolated, and my laptop powered the transmitter. Voila! Immediately the 60Hz pickup disappeared, and the spikes were now even larger. Win-win!</p>
<p class="caption">Receiver readings. Spikes are 1s, and flat bits are 0s. Note the lack of 60Hz noise: when it's flat, it's flat. </p>
<p><img alt="Receiver readings. Spikes are 1s, and flat bits are 0s. Note the lack of 60Hz noise: when it's flat, it's flat. " src="/static/bcc2_everyhalfsecondIR.png"/></p>
<p>I think that the chassis/external cabling of my laptop allowed it to pick up a lot more noise than the isolated Arduino on its own. When the computer powered the receiver, the reciever gets a lot of that noise passed into it. However in this second config, the receiver seems to not pick up much noise on its own.</p>
<hr/>
<h1 id="interpreting-spike-clusters-as-bits">Interpreting spike clusters as bits</h1>
<h3>Through in-line capacitor vs. without</h3>
<p>If you recall, I'm linking my receiver's analog input with GND with a high value resistor. I wanted to compare touching the analog pin directly with touching it through a capacitor. I expected to be able to charge the capacitor and sort of integrate the spikes into a more digital looking bit, but it seems to not help that much.</p>
<p class="caption">Receiver readings when touching through in-line capacitor</p>
<p><img alt="Receiver readings when touching through in-line capacitor" src="/static/bcc2_withinlinecapIR.png"/></p>
<p class="caption">Receiver readings when touching analog pin directly</p>
<p><img alt="Receiver readings when touching analog pin directly" src="/static/bcc2_withoutinlinecapIR.png"/></p>
<p>Using a capacitor first seems to increase the overall signal by a tiny amount. Or it could be selection bias! Either way, I don't think it matters too much. </p>
<h3>Software integration</h3>
<p>I then tried to just keep a moving average of the analog readings. This is a 10-sample ring buffer that only averages the past 10 readings. The noise on the 0s has decreased a bit in the averaged output. The spike clusters are also now clearer, but at what cost? The highest point is now around 150 instead of 250. Not so bad. I'll probably choose a max filter instead to better approximate a logic signal.</p>
<p class="caption">Short-term moving average</p>
<p><img alt="Short-term moving average" src="/static/bcc2_withsmoothingIR.png"/></p>
<p>I also tried a counting filter which output 700 if there were more than 5 samples &gt; 100 in the past 10 seconds, and 0 otherwise. </p>
<p class="caption">Boolean filter of (num samples &gt; 100) &gt; 5</p>
<p><img alt="Boolean filter of (num samples &gt; 100) &gt; 5" src="/static/bcc2_countingfilterIS.png"/></p>
<h1 id="highest-speed">Highest speed</h1>
<p>The fastest speed I tried was a bitrate of 50Hz. A pulse train was sent every 10ms, then not sent out for 10ms. Looks good, there are at least 5 received bits within each window, and I think a human would be able to pretty clearly differentiate between 1s and 0s. </p>
<p class="caption">50Hz train of pulses transferring between the transmitter and reciever</p>
<p><img alt="50Hz train of pulses transferring between the transmitter and reciever" src="/static/bcc2_every10msIR.png"/></p>
<h1 id="conclusion-and-next-steps">Conclusion and next steps</h1>
<p>I just want to make it clear, this method sucks. It's really hacky, and the receiver needs to have a much better and faster analog-to-digital converter for it to properly receive the spikes. I'm trying it out with a better microcontroller soon, and we'll see if the spikes can be made even tighter. I want a data rate of at least 1000 bits/sec, and it would be <em>amazing</em> to hit 40 kilobits/sec since that's a typical RFID data transfer rate. But at least it works without a shared ground now!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/bcc2/</guid>
      <pubDate>Tue, 27 Apr 2021 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Body Channel Communication Tutorial Pt. 1</title>
      <link>https://andykong.org/blog/bcc1/</link>
      <description>Harnessing the body's conductivity as a signal path</description>
      <content:encoded><![CDATA[<html><body><p>Hello! Today I'm going to be talking about some preliminary experiments I did involving body channel communication, or BCC. BCC is a technique that uses the body's natural conductivity as a signal path, allowing your devices to talk to anything you touch. I implemented a basic version that requires a shared ground, some experiments from an implementation without a shared ground, and show practical techniques that worked for me. </p>
<p class="caption">Transmitting signal over the skin</p>
<p><img alt="Serial plotter transmitting signal over the skin" src="/static/bcc1_signaloverskin.png"/></p>
<p><br/></p>
<h1 id="background">Background</h1>
<hr/>
<h3>Why would you want this?</h3>
<p>Despite your body's high resistance, large voltage changes propogate fairly well across it. If you wear a watch or some other device, it can digitally write out an I2C/UART/bitwise ASCII signal to a pin that's touching your skin by bringing the pin high and low. This digital logic signal propagates through you to whatever you're touching. </p>
<p>If the device sends out your student ID, anything device you touch can now identify who is touching it. If it sends out your credit card information, you can pay with just a touch when checking out.</p>
<p>Best of all, it works the other way. If a device is writing bits out to a surface, your watch can also detect when you touch something. If each device transmitted its own MAC address, your watch would know what you touched, and would be able to talk back to it through you. </p>
<p><br/></p>
<h3>But doesn't sending a signal require two wires for ground and signal?</h3>
<p>Very astute! It does! However, because the body is so conductive, it naturally couples capacitively to the environment around it. This means that the device and the human can share a ground connection without an additional wire; the device is grounded through the building's wiring, and the human through their salty body.</p>
<p>The downside to this is that the ground connection is rather weak. The human-ground capacitor is estimated to be about 100pF or less, so signals don't cross the human-device THAT well. However, there has been research exporing how to robustly use BCC even with this weak connection. More details can be found in <a href="http://www.alansonsample.com/publications/docs/2018%20-%20UIST%20-%20Enabling%20Interactive%20Infrastructure%20with%20BCC.pdf">[1]</a> and <a href="http://www.alansonsample.com/research/BCC.html">[2]</a>. </p>
<p><br/></p>
<h1 id="what-i-did">What I did</h1>
<hr/>
<h2 id="hardware">Hardware</h2>
<p>My setup is very simple. I used two Arduinos, two 180kΩ resistors, and two jumper wires. There is a "sender" Arduino imitating a smartwatch, and a receiver Arduino imitating a payment terminal or another interactive device. </p>
<p>The sender has just a single jumper out, and it's digital writing a 10Hz square wave to the jumper wire.</p>
<p class="caption">Sender Arduino</p>
<p><img alt="Arduino with jumper wire outputting the digital signal" src="/static/bcc1_sender.jpg"/></p>
<p>The receiver has a jumper wire on analog pin 0 which is reading at 100Hz. A 360kΩ resistor connects that pin to the ground pin. The extra resistor at the bottom is for measuring a ground truth analog reading, which I'll talk about later.</p>
<p class="caption">Receiver Arduino</p>
<p><img alt="Arduino with jumper wire on analog pin, which is connected to the ground by resistor" src="/static/bcc1_receiver.jpg"/></p>
<p>The resistor is necessary because of the EMI pickup when I added the jumper wire to the analog pin. Here's the noise on the analog pin, then with the jumper, then when I hold the jumper with my finger. </p>
<p class="caption">Baseline analog pin readings</p>
<p><img alt="Serial plotter of baseline analog pin readings" src="/static/bcc1_baselineanalognoise.png"/></p>
<p class="caption">Analog pin readings with jumper wire attached</p>
<p><img alt="Serial plotter of analog pin readings with jumper wire attached" src="/static/bcc1_baselinewithwire.png"/></p>
<p class="caption">Analog pin readings with human holding jumper wire</p>
<p><img alt="Serial plotter of analog pin readings with human holding jumper wire" src="/static/bcc1_baselinetouchinganalognoise.png"/></p>
<p>The human body acts as an antenna for 60Hz noise, and my body is no different. The spikes you see in the last image are from my environment, and would destroy our signal without the resistor to ground. The smaller the resistor, the easier the environment's noise gets leaked away from the pin. However, this easier leakage also reduces our overall signal more, so I picked a large resistor that approximately matched my skin's resistance. </p>
<p><br/></p>
<h2 id="signal-shared-ground">Signal (Shared Ground)</h2>
<p class="caption">Shared ground BCC setup (technically they're already connected through the laptop's ground)</p>
<p><img alt="Shared ground setup (technically they're already connected through the laptop's ground)" src="/static/bcc1_connectedground.jpg"/></p>
<p>To transfer the signal, I touched the output jumper of the sending Arduino, then touched the input jumper of the receiving Arduino. I also pressed the output jumper directly against my other input, to capture a reference for what the output pin's signal looks like directly. </p>
<p class="caption">Square wave being transferred over human touch. Green is the pin I'm touching, red is the reference pin</p>
<p><img alt="Serial plotter of square wave being transferred over human touch" src="/static/bcc1_signaloverskin.png"/></p>
<p>The signal transfer is pretty clear. The green line is what the receiver reads from my finger, and the red is the actual output of the pin. There's a bit of interference from the ADC, but even without the reference pin the signal transfers really cleanly.</p>
<p class="caption">Square wave being transferred over human touch sans reference</p>
<p><img alt="Square wave being transferred over human touch without reference" src="/static/bcc1_signaloverskin1.png"/></p>
<p>Here's what the noise looks like in the setup if I hold the receiving pin without touching the signal pin. </p>
<p class="caption">Noise when touching receiver pin without touching signal pin</p>
<p><img alt="Noise when touching receiver pin without touching signal pin" src="/static/bcc1_nosignaloverskin.png"/></p>
<p>The no-touch signal is a bit noisier than I would like, but definitely different enough from the touching signal that we would be able to detect it. </p>
<p><br/></p>
<h2 id="signal-no-shared-ground">Signal (No Shared Ground)</h2>
<p>I connected the sending Arduino to another computer when I did this experiment. The receiver is plugged into my laptop, which was not connected to the wall outlet at the time.</p>
<p class="caption">No shared ground BCC setup</p>
<p><img alt="No shared ground setup" src="/static/bcc1_connectednoground.jpg"/></p>
<p>Without the shared ground of my laptop, the problem gets a lot harder. I first tried holding the signal and receiving wires to compare the shared vs unshared grounds. The square wave is lost in big noise of a completely different frequency, and comes straight back when I connect the grounds again.</p>
<p class="caption">Unconnected vs. connected ground BCC signal</p>
<p><img alt="Unconnected vs. connected ground BCC signal" src="/static/bcc1_ngvsg.png"/></p>
<p>I was worried that the signal wasn't actually getting through at all, but it does. I tried holding the signal wire, then dropping it but still touching the receiving wire. </p>
<p class="caption">Received signal while I hold the signal wire then release it</p>
<p><img alt="Received signal while I hold the signal wire vs when I don't" src="/static/bcc1_ng_touchvsnotouch.png"/></p>
<p>And when I hold the receiving wire, then drop it, the signal goes to our nice analog baseline. </p>
<p class="caption">Received signal while I hold the receiving wire then release it</p>
<p><img alt="Received signal while I hold the receiving wire then release it" src="/static/bcc1_ng_touchvsnotouch2.png"/></p>
<p>The detection is sizing up to be fairly tricky, but I think that we should be able to detect the signal even when the grounds are not shared. There's already a clear difference between touching signal and not, but we need to do some work on conditioning the signal to better cross the human-pin divide.</p>
<h1 id="next-steps">Next steps</h1>
<p>This is a 10Hz signal, which is fairly slow. We can make it much higher, but I needed to go low to show the plots properly on the serial plotter. However, we're limited by the ADC time, which is middling on an Arduino. We may need to switch to another microcontroller if we want more speed. With a higher frequency, we also get better coupling to ground, which hopefully gives us a better transferred signal.</p>
<p>We're gonna try bits next! Cya then!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/bcc1/</guid>
      <pubDate>Sat, 17 Apr 2021 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Intro to Intro to Woodworking</title>
      <link>https://andykong.org/blog/woodworkingday1/</link>
      <description>There's always money in the banana stand</description>
      <content:encoded><![CDATA[<html><body><p>Yesterday I had my 2nd in-person class this semester. We met a nice man in Techspark and he showed us the woodshop. The walls were covered in covered plywood shelves and things. We saw a few demo projects that we'd be doing during the course, like a folding stool, push stick, and crate. </p>
<p>Our first assignment was to make a little banana stand/hammock thing. We went over 4 machines — the band saw, belt and disk sander, oscillating spindle sander, and drill press. I had never seen a spindle sander before, but it was a really cool machine. It's really good for sanding down interior curves, which neither the belt nor disk sander can reach. </p>
<p>I ended up using the bandsaw a lot. The cut turn radius is quite good, and I learned about using relief cuts to make the cuts even tighter. The bandsaw is also good for widening slots, and I widened my stand's base a bit too much. Ended up having to glue it in place, using some wood filler to make the seams nicer looking.</p>
<p class="caption">Photo of my banana stand, hookless and bananaless</p>
<p><img alt="Photo of my banana stand, hookless and bananaless" src="/static/bananastand.png"/></p>
<p>Finally, I used a hand sander which had a velcro pad of sandpaper that was a little off center to smooth the rough wooden surface. It looks quite weird when it moves, wagon-wheeling before your eyes. </p>
<p>It's amazing how much scrap wood they have in there. The shop goes through a lot just for demonstrations, and then thick plates are just free for anyone to use in the refuse pile. Good news for me! I'm not sure what to make next — it would be cool to recreate a lamp of mine, or a new wooden spoon. So many possibilities!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/woodworkingday1/</guid>
      <pubDate>Wed, 24 Mar 2021 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>MPR121 Touch Sensing Across Barriers</title>
      <link>https://andykong.org/blog/mpr121passthrough/</link>
      <description>Testing capacitive coupling across various barriers</description>
      <content:encoded><![CDATA[<html><body><p>Hello! Today I received the MPR121, a nifty little 12-channel touch sensor:</p>
<p class="caption">Glamour shot of the MPR121 touch sensor</p>
<p><img alt="Glamour shot of the MPR121 touch sensor" src="/static/mpr121glamour.png"/></p>
<p>The chip is pretty simple, uses I2C to measure each pin's capacitance using the charge then discharge trick I used <a href="../touchreactiveLEDs/">here</a>. It gives a touch/no touch output, or the raw sensor values per each pin (ranges from 0-400ish). Also has a built-in filtered output that provides smoother data if you want it. </p>
<p>The setup is easy and not very interesting since the chip is so single-purpose. I'm much more interested in showing you how well the sensor detects touch through a barrier. </p>
<h2 id="testing-materials">Testing materials</h2>
<p>I used what I had near me, and those were: saran wrap, masking tape, and two kinds of Scotch tape. Here's the table.</p>
<p class="caption"></p>
<p><img alt="Table of sensor outputs for the MPR121" src="/static/mpr121chart.png"/></p>
<p>As mentioned before, the sensor values range from 0-400. On the bare surface I was able to get down to 48, which is a big SNR! None of the others even came close to that, only dipping 10% of the max value for the Saran wrap. This was probably the thinnest barrier I tried, and even that was too much. Despite the low SNR, the sensor still detected my touches consistently when separated by most of the materials I tried. Works great! :)</p>
<p>The capacititance didn't drift that much across my trials (~15 minutes), so I'm not so worried about needing to recalibrate very frequently. These types of biosensors are usually pretty finicky in that regard, so I'm glad. I also didn't try fabric or any porous material because I needed this to be waterproof, but it'd be cool if YOU (dear reader) did, and then told me about it!</p>
<p>That's all for now! Cya!</p>
<p class="caption">MPR121 wrapped in saran wrap during testing</p>
<p><img alt="MPR121 wrapped in saran wrap during testing" src="/static/mpr121saran.png"/></p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/mpr121passthrough/</guid>
      <pubDate>Sat, 13 Mar 2021 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>5050 LED Strip Teardown</title>
      <link>https://andykong.org/blog/led5050teardown/</link>
      <description>Repurposing an LED light strip controller and power supply</description>
      <content:encoded><![CDATA[<html><body><p>Hello! I purchased some LED strips off Amazon. I've always wanted some more lighting, but more importantly, I also wanted to set up some interactive visuals in my room! </p>
<p class="caption">Let there be light</p>
<p><img alt="Let there be light" src="/static/led5050_lit.png"/></p>
<p>Ideally, I'd create a few scripts that allow users to control the light strip in fun ways. For instance, I wanted to add an encoder dial that would control the position of a single LED, and then when turned, it would just move around strip in a big circle. Or a visual doorknob that would flash when pressed, so I could respond to people outside my door while listening to music. Or a clock that fills up or drains the LED strip as the day progresses, a visual indicator of daylight running out. There's a lot of ideas here.</p>
<p>Anyway, the first step is to purchase a sufficiently long LED strip off Amazon. I measured my room, and need about 50 feet of lights to go all the way around. </p>
<p>Buyer beware here, if the item description does not mention "chase effect" and is really cheap, it probably doesn't have individually addressable LEDs. They're referred to as 5050 LEDs, and instead of 3 wires have 4 wires — 12 V, R, G and B. Not sure why it's inverted like that. So since the strip is only addressable as a whole, I can't do the knob thing, but I can do some more fun things that I had planned.</p>
<p class="caption">Unlit reel on my desk. Comes with backing and a nice rubber-y cover. </p>
<p><img alt="Unlit reel on my desk. Comes with backing and a nice rubber-y cover." src="/static/led5050_reel.jpg"/></p>
<p>Most of these come with some app, so we know there will be some Bluetooth or otherwise IoT going on inside. The supply is 12V, and claims to source up to 5 amps. At 60mA per LED at full draw, and 300 LEDs/reel (2 reels), that's a max draw of 36 A. Of course, each LED does not draw 60mA, but we are upper bounding. Though that 60mA was for NeoPixels, maybe it's lower for these...</p>
<h2 id="power">Power</h2>
<p>Anyway, here's the power supply. Looks like a generic 12V brick, some conversion then a Cockcraft-Walton (I assume that's what the diodes are for?), then another transformer. Take this with a grain of salt, I study computers not circuits. Out comes 14V-ish DC (no load), and 7V goes into the emitter of the transistors in bottom-left.</p>
<p class="caption">Power supply for the LED lights</p>
<p><img alt="Power supply for the LED lights" src="/static/led5050_powersupply.jpg"/></p>
<h2 id="control">Control</h2>
<p>Lying next to the power supply board unsecured is the control board. You can see the 3 transistors in the bottom left leading to some FAT traces that go to the RGB channels, as well as the 12V input. There's also the board-trace Bluetooth antenna in the top right-hand side.</p>
<p>I couldn't find the transistor number online (X0GA 25), but if you apply 4V to the bottom-right pin of the SOT package, it lights. Good enough for me. I may also just turn all the transistors on and then put my own transistors on the output line.</p>
<p class="caption">Controlling board for the LED light strip. Was previously just dangling in the power brick...</p>
<p><img alt="Controlling board for the LED light strip. Was previously just dangling in the power brick..." src="/static/led5050_controlboard.jpg"/></p>
<p>Looks good. Next step, find a 5V microcontroller and hook it up! We'll see how hard the controls are, depending on the voltage-to-brightness conversion on the transistor.</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/led5050teardown/</guid>
      <pubDate>Sat, 20 Feb 2021 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Blink Detection Using Infrared Reflective Sensors</title>
      <link>https://andykong.org/blog/blinkdetectioninitial/</link>
      <description>Experiments in focus tracking.</description>
      <content:encoded><![CDATA[<html><body><p>Hello! </p>
<p>Recently I found out that blink frequency is associated with focus — when shown a more engaging and interesting video, participants tended to blink less. Intuitively this makes sense; if you want to see something, you'll naturally spend less time closing your eyes. I wanted to apply this principle to something more general: living life. But I didn't want to use computer vision! First, face detection is hard to do well, and runs pretty slowly if you also include closed vs. open eyes. I also wouldn't be able to track my blink rate while doing things away from the computer (and believe it or not, much of my life <em>is</em> spent away from the computer). So CV was out. </p>
<p>I had read from a few papers that skin absorbed infrared light super well, so it shows up dark under an infrared camera. I looked around, and sure enough people had been detecting blink with an IR LED and detector situated very closely to the eyes. Since the shiny part of your eyeball is not skin, it reflects a lot of infrared. When you blink, this gets blocked, and the signal you're reading will have a sharp drop. And that's exactly what I did!</p>
<h2 id="setup">Setup</h2>
<p>I ordered some of these IR reflective sensors off Amazon — these are usually used for line following robots or really terrible distance sensors (their output depends on the nearby material's reflectance). </p>
<p class="caption">IR reflectance sensors. 4 pin, one LED and one phototransistor with a light filter.</p>
<p><img alt="IR reflectance sensors. 4 pin, one LED and one phototransistor with a light filter." src="/static/blinktrack1_reflectancesensor.png"/></p>
<p>I then wired it up. LED is current-limited by 100 ohms, phototransistor has 1KΩ in series. I think I can reduce this value to make it more sensitive, since it'll pull down to ground more easily.  </p>
<p>Here it is at work: (first time I've hosted a video locally on this site!)</p>
<div style="text-align:center;">
<video controls="" height="240" width="320">
<source src="/static/blinktrack1.mp4" type="video/mp4">
Your browser does not support the video tag.
</source></video>
</div>
<p>Anyway, signal is pretty clear (spans around 1.5V on the 5V ADC), and isn't too noisy. I have to figure out a way to stop my pupil from messing with the signal, but I'll mount it on some glasses first and see how stable it stays. </p>
<p>Eventually, I want to push these blink timestamps constantly to a database, and have it ping me when it notices me slipping out of focus. I dunno, something can be done with this additional biometric data. </p>
<p>That's all for now. Cya!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/blinkdetectioninitial/</guid>
      <pubDate>Fri, 12 Feb 2021 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Calculating the expected value of the PA scratch offs</title>
      <link>https://andykong.org/blog/palotteryexpectedvalue/</link>
      <description>It's ok, all proceeds pay for the benefits of older residents of Pennsylvania</description>
      <content:encoded><![CDATA[<html><body><p>Hi! Yesterday I had a dream where I won a math prize (unlikely) and received a prize of 200 million dollars (even more unlikely). I then woke up, and decided that I needed to lose 20 dollars immediately on the Pennsylvania lottery (I think the odds of this one are the most unlikely out of all three occurences). </p>
<p>After getting a bunch of scratch off dust all over my bedroom carpet, I ended up $20 poorer, and decided to calculate the expected value of each game I played. The <a href="https://www.palottery.state.pa.us/Scratch-Offs/Active-Games.aspx">website</a> lists the chances of winning at all (usually around 1:3 to 1:5), then also has a pdf in each page that lists "Chances of Winning" for that game in particular. </p>
<p>A brief aside, some lotteries will show your odds instead of chances. This is SIGNIFICANTLY different. Odds are wins:losses, instead of wins/(wins+losses), which in this case would change 1:3 to 1/4. </p>
<p class="caption">An example "Chances of Winning" pdf from a real active game in PA as of 2/4/21</p>
<p><img alt='An example "Chances of Winning" pdf from a real active game in PA as of 2/4/21' src="/static/palotterychances.png"/></p>
<p>PA Lottery offers scratch-offs that cost 1, 2, 3, 5, 10, 20, and 30 dollars. I took the most recent game of each value, and added up the expected values (prize amount * probability of winning prize). I know it doesn't generalize well since I'm only using one of each cost level, but YOU try to scrape a PDF with variable rows. Have fun with that. This data was collected on 2/3/21 from the <a href="https://www.palottery.state.pa.us/Scratch-Offs/Active-Games.aspx">PA Lottery site</a>.</p>
<style type="text/css">
.tg  {border-collapse:collapse;border-spacing:0;margin-left: auto; margin-right: auto;}
.tg td{border-color:white;border-style:solid;border-width:1px;font-family:Arial, sans-serif;font-size:14px;
  overflow:hidden;padding:10px 5px;word-break:normal;}
.tg th{border-color:white;border-style:solid;border-width:1px;font-family:Arial, sans-serif;font-size:14px;
  font-weight:normal;overflow:hidden;padding:10px 5px;word-break:normal;}
.tg .tg-0lax{text-align:left;vertical-align:top}
</style>
<table class="tg">
<thead>
<tr>
<th class="tg-0pky">Scratch-Off Game</th>
<th class="tg-0pky">Cost</th>
<th class="tg-0pky">Expected Value</th>
<th class="tg-0pky">Net Loss</th>
<th class="tg-0pky">% Change</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tg-0pky">3 Mil Extra</td>
<td class="tg-0pky">$30</td>
<td class="tg-0pky">$23.10</td>
<td class="tg-0pky">-$6.9</td>
<td class="tg-0pky">-23%</td>
</tr>
<tr>
<td class="tg-0pky">1 Mil Jack</td>
<td class="tg-0pky">$20</td>
<td class="tg-0pky">$15.01</td>
<td class="tg-0pky">-$4.99</td>
<td class="tg-0pky">-25%</td>
</tr>
<tr>
<td class="tg-0pky">WIN WIN WIN</td>
<td class="tg-0pky">$10</td>
<td class="tg-0pky">$7.31</td>
<td class="tg-0pky">-$2.69</td>
<td class="tg-0pky">-26.9%</td>
</tr>
<tr>
<td class="tg-0lax">Leprechaun</td>
<td class="tg-0lax">$5</td>
<td class="tg-0lax">$3.52</td>
<td class="tg-0lax">-$1.48</td>
<td class="tg-0lax">-29.6%</td>
</tr>
<tr>
<td class="tg-0lax">Wild Cash</td>
<td class="tg-0lax">$3</td>
<td class="tg-0lax">$1.98</td>
<td class="tg-0lax">-$1.02</td>
<td class="tg-0lax">-34%</td>
</tr>
<tr>
<td class="tg-0lax">O'Lucky Coin</td>
<td class="tg-0lax">$2</td>
<td class="tg-0lax">$1.31</td>
<td class="tg-0lax">-$0.69</td>
<td class="tg-0lax">-34.5%</td>
</tr>
<tr>
<td class="tg-0lax">Clover All Over</td>
<td class="tg-0lax">$1</td>
<td class="tg-0lax">$0.71</td>
<td class="tg-0lax">-$0.29</td>
<td class="tg-0lax">-29%</td>
</tr>
</tbody>
</table>
<p><br/></p>
<p>Results seem as expected, the more you pay, the less you lose proportionally, though not in raw cash. And, of course, house always wins. The calculated chances also reflect this, with the $30 dollar games offering the highest chances of winning anything. </p>
<p>Weirdly enough, it seems that the loss from $1 games is the same as loss from $5 games, which makes you feel like you should buy 5x 1$ games instead of 1x 5$ game. Whatever gets you going, I guess. You lose money either way.</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/palotteryexpectedvalue/</guid>
      <pubDate>Thu, 04 Feb 2021 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Detecting instantaneous frequency is hard</title>
      <link>https://andykong.org/blog/IFproblems/</link>
      <description>Major blockades to fast, effective BCI</description>
      <content:encoded><![CDATA[<html><body><p>Hi! Today we're going to be talking about FFT bins and time. </p>
<p>When you first take an FFT, whether it be <code>scipy.fft.rfft</code> or MATLAB's built-in, you may have had to convert from a "bin number" to the frequency range that bin represented. This formula is pretty simple, but might've been tricky to google initially; the Nth bin contains frequencies from N x (sample rate/sample length) to (N+1) x (sample rate/sample length). </p>
<p>If you were like me, you spent some time staring at the constant (sample rate/sample length) term that each bin was getting multiplied by. The surprising part was that this ratio was independent of sample rate, meaning that the frequency resolution you got from the FFT is entirely dependent on how long the signal is sampled and nothing else. Of course, the total NUMBER of bins you can get is increased if the sample rate goes up (up to the Nyquist frequency of sample rate/2), but that doesn't help you if your signal is in the low Hz. </p>
<p>Now, if you do RF stuff, this hardly matters. FM radio operates on increments of 0.1Mhz, so a 10 µs sample length is sufficient to get the bin resolution you need. This is basically finding the instantaneously frequency of a signal — we don't notice 10 µs at all. </p>
<p>The problem arises when you start analyzing the frequency content of EEG, which operates strictly under 100Hz, and usually under 20Hz. Now to get 1Hz bin resolution in your peak detection, you need at least 1 second of data. This is fine for research-grade BCI, but ruins usability when BCI takes 4x the time to press a button and is only 80% reliable. </p>
<p>Besides that, the number of inputs is reduced, since only 1 Hz differences can be detected. This leaves us with the whole Hz increments from 10-20Hz, which is only about 10 options. If the possible frequency bins were doubled, then there would be twice inputs; however, to get 0.5Hz bins you'd need 2 seconds of data. These are hardly instantaneous methods now, and EEG doesn't become any more reliable with tighter frequency bins.</p>
<p>And don't even get me started on the time-frequency resolution tradeoff. EEG signals are incredibly temporal, meaning a 10Hz signal may only exist for a second within a longer period of recorded data. This is why techniques like phase-rectified signal averaging work so well for it. But that's another post.</p>
<p>Bye!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/IFproblems/</guid>
      <pubDate>Sun, 31 Jan 2021 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Touch Reactive LEDs</title>
      <link>https://andykong.org/blog/touchreactiveLEDs/</link>
      <description>Electronic piano, anyone?</description>
      <content:encoded><![CDATA[<html><body><p>Hi! Today I’m going to go over a small Arduino project I made using LEDs and the <a href="">FastTouch library</a>.</p>
<p>It's a row of LEDs mounted in protoboard that lights up when you touch the front lead. Multiple can be touched at one time. The only interesting part of the code is allowing each pin to both detect touch and power the LED. Here's why.</p>
<blockquote class="twitter-tweet tw-align-center"><p dir="ltr" lang="en">Made a little row of LEDs touch-sensitive. Tracks with your finger really fast! <a href="https://t.co/C6R0Aa9zEK">pic.twitter.com/C6R0Aa9zEK</a></p>— Andy Kong (@redlightguru) <a href="https://twitter.com/redlightguru/status/1353184760026296320?ref_src=twsrc%5Etfw">January 24, 2021</a></blockquote>
<script async="" charset="utf-8" src="https://platform.twitter.com/widgets.js"></script>
<h2 id="how-fasttouch-detects-human-touch">How FastTouch detects human touch</h2>
<p>So the way the FastTouch library works is by detecting the capacitance of the human body, which is a few hundred picofarad. When assigned to a pin, it configures the pin it's reading as <code>INPUT_PULLUP</code>, which means it's held at 5V by a ~10k internal resistor. Then it changes the pin to <code>OUTPUT</code> and writes the pin as LOW, and counts how long that pin takes to hit 0V. If there's a human touching the pin, we're holding some of the charge from the 5V on the pin, so it takes longer to discharge to 0V. If we're not touching it, then it should discharge almost immediately. </p>
<p>This works best if the human is touching the ground of the Arduino with their other hand, but also works without that because we are capacitively coupled to our environment, and the ground of the Arduino through the air (something I don't understand well enough to explain here). </p>
<p class="caption">Top, unlit LEDs</p>
<p><img alt="Top, unlit LEDs" src="/static/touchLEDs1toppic.png"/></p>
<h2 id="problem">Problem</h2>
<p>Because the library counts how long each pin takes to discharge, if we just wired an LED+resistor in series from the pin output to ground, it would discharge immediately through the LED and read no human touch. If I had used one pin for the touch and one pin for the LED, I'd only be able to do 6 LEDs because the Arduino only has around 12 usable pins. So what did we do?</p>
<h2 id="solution">Solution</h2>
<p>Instead of wiring the LED to ground, each one is wired to a pin that is held <code>HIGH</code> when we check the touch, then brought <code>LOW</code> (ground) when we light the LED. Because the LED prevents backwards current flow, it's an open circuit when checking touch, then allows forward current to light it up when indicating touch detection. Boom! 2x reduction in pins.</p>
<p class="caption">Picture of the underside of the board. Alternating resistors</p>
<p><img alt="Picture of the underside of the board. Alternating resistors" src="/static/touchLEDs1resistorunderside.png"/></p>
<h2 id="construction">Construction</h2>
<p>This took me like two hours to solder and put together, mainly because A) the LEDs had to be in front of the finger pads for visibility, but the pins behind the LEDs had to connect to the finger pads in front, and B) I wanted it to look nice. </p>
<p>That's all for now, take care.</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/touchreactiveLEDs/</guid>
      <pubDate>Fri, 29 Jan 2021 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Oversampling vs. Averaging for Noise Reduction</title>
      <link>https://andykong.org/blog/oversamplingvsaveraging/</link>
      <description>Spoiler: always oversample.</description>
      <content:encoded><![CDATA[<html><body><p>Hi! Today I was curious about the effects of oversampling a single, high sample frequency channel of data vs 4 simultaneously sampling channels of the same data, and which would give better data. This ties into my work on EEG processing, where I have the chance to use up to 4 channels but don't want to just average all of them because that seems too simplistic. </p>
<h2 id="what-did-i-try-then">What did I try then?</h2>
<p>So, I generated a sine wave at a low-ish frequency (50Hz) of amplitude 1, then added Gaussian noise of power 10 to it. It has duration 1 second, but I created it using a 100kHz "sample frequency". Here's what it looks like. </p>
<p class="caption">Graph of the original and corrupted signal. Orange is the original</p>
<p><img alt="Graph of the original and corrupted signal. Orange is the original" src="/static/ovacombined.png"/></p>
<p>I then took 1 sample every n samples in order to create two downsampled data streams. One was sampled every 100 samples, giving a sample rate of 1kHz. The other took one point every 500 points, and had a sample rate of 200Hz. I then compared their FFTs.</p>
<p class="caption">Spectrum of the 200Hz sampled signal vs the 1kHz sampled signal. Blue is the 200Hz.</p>
<p><img alt="Spectrum of the 200Hz sampled signal vs the 1kHz sampled signal. Blue is the 200Hz." src="/static/ova1kvs200zoomout.png"/></p>
<p class="caption">Spectrum of the 200Hz sampled signal vs the 1kHz sampled signal, zoomed in to show the difference. Blue is the 200Hz, and much noisier.</p>
<p><img alt="Spectrum of the 200Hz sampled signal vs the 1kHz sampled signal, zoomed in to show the difference. Blue is the 200Hz, and much noisier." src="/static/ova1kvs200.png"/></p>
<p>I've scaled the peaks so they line up, but we see clearly that the 1kHz signal (orange) has much lower noise compared to its peak compared to the 200Hz signal. Let's see what happens if we downsample the 1kHz signal down to 200Hz and compare spectrums then.</p>
<p class="caption">Blue is the 1kHz signal downsampled to 200Hz, orange is the original 200Hz sampled signal. The downsampled data is much less noisy</p>
<p><img alt="Blue is the 1kHz signal downsampled to 200Hz, orange is the original 200Hz sampled signal. The downsampled data is much less noisy" src="/static/ovadownsample.png"/></p>
<p>We see that the downsampled data beats the 200Hz anyway, even though they're at the same length now and sample frequency now. </p>
<p>What if the 200Hz sampling had 4 unique channels? These are created frmo the original 100kHz signal, and are side by side. What if they were all averaged together to reduce the Gaussian noise (simulating averaging channels)?</p>
<p class="caption">Blue is the 4-channel average, each 200Hz. Orange is the downsampled 1kHz spectrum.</p>
<p><img alt="Blue is the 4-channel average, each 200Hz. Orange is the downsampled 1kHz spectrum." src="/static/ovaavgvsdown.png"/></p>
<p>Blue is the averaged, orange is the downsampled spectrum. We see that the noise has gone down by a lot in the original 200Hz spectrum, but it's still higher than the oversampled one by about a factor of 2 (just from comparing peaks visually). Here's the same plot including the original, unaveraged 200Hz channel.</p>
<p class="caption">Same as the last graph, but with the original 200Hz sampled spectrum in green. </p>
<p><img alt="Same as the last graph, but with the original 200Hz sampled spectrum in green. " src="/static/ovaavgvsdownplusorig.png"/></p>
<p>Though I didn't scale the peaks, we see that the noise power has gone down by a factor of 2 (makes sense because of the Gaussian noise), but it still doesn't beat the downsampled 1kHz data. Even if we lower the original signal frequency, the oversampled one continues to outperform the multi-channel setup.</p>
<p>The orange is actually the averaged one here, it has higher noise peaks than the original signal.</p>
<p class="caption">At a lower signal frequency, the downsampled 1kHz signal still has the lowest noise (green). The averaged 4-channel signal has even higher noise than the original 200Hz signal actually (orange vs. blue).</p>
<p><img alt="At a lower signal frequency, the downsampled 1kHz signal still has the lowest noise (green). The averaged 4-channel signal has even higher noise than the original 200Hz signal actually (orange vs. blue)." src="/static/ovalowfreq.png"/></p>
<h2 id="discussion">Discussion</h2>
<p>I didn't think the effect would be so drastic, but it seems that sampling at a high rate then downsampling is a very effective strategy compared to averaging all your channels together. But then I don't know what to do with my 4 channels then...</p>
<p>This result is counterintuitive because having 5, 200Hz channels and averaging them is equivalent to sampling at 1kHz and downsampling. </p>
<p>If we imagine a 1kHz sampled signal, what we did to generate the 200Hz signal was take 1 of every five sample points. This means if we had 5 “channels” (each offset by 1 from the beginning), then its equivalent to down sampling the 1khz signal. But it’s weird that using 4 I didn’t have nearly the same effect, and I don't expect going to 5 channels to change much. </p>
<p>It might be different because our 200Hz channels were taken from the original signal, which was created at a much higher sample rate (100kHz), instead of taking them frmo the 1kHz signal. But it seems like a minor difference, honestly. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/oversamplingvsaveraging/</guid>
      <pubDate>Sun, 24 Jan 2021 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Comparing the interface speed of a thermostat to a keyboard</title>
      <link>https://andykong.org/blog/interfacespeeds/</link>
      <description>Calculating the human-computer bandwidth of common and uncommon input devices using information transfer rate</description>
      <content:encoded><![CDATA[<html><body><p>A lot of brain-computer interface(BCI) researchers will express their results in terms of how much information the user can input in one minute, known as information transfer rate (ITR), measured in bits per minute.</p>
<p>I haven't yet seen a compiled list of the ITR of common household interfaces, so I thought I'd make one. This list will only cover INPUT bits/min, so I'm not going to be calculating how much text you can read off a screen or a number readout since that's OUTPUT information.</p>
<p>I'll list each device, explain its peculiarities and any choices I made regarding number of inputs, then give the stats. Here's a table of the results, read on for how I came to each number!</p>
<h3>Definition of bits for ITR</h3>
<p>Before we start, it may seem strange to quantify the mouse or touchscreen in terms of bits, since they're almost continuous input devices. Here, "bits" refers to the log2 of the number of options available. For example, if a user in a BCI trial is trying to select one button when there are two available to pick, then they transmit one "bit" of information per correct choice they make. If choosing a button takes the user 5 seconds, then they'll be able to make 12 choices in a minute. making 12 bits per minute the ITR.</p>
<h2 id="compiled-tables-both-available-at-page-bottom">Compiled Tables (Both available at page bottom)</h2>
<p class="caption">Table of information transfer rates of all common devices</p>
<p><img alt="Table of information transfer rates of all common devices" src="/static/bpmITRCommon.png"/></p>
<p>Let's get into it!</p>
<h1 id="list-of-common-input-devices">List of Common Input Devices</h1>
<hr/>
<h2 id="thermostat">Thermostat</h2>
<p class="caption">Picture of a thermostat</p>
<p><img alt="Picture of a thermostat" src="/static/bpmthermostat.jpg"/></p>
<p>I pressed 10 buttons in 6.4 seconds. </p>
<h5># of Inputs: 5</h5>
<h5>Input Speed: 1.5/sec</h5>
<h5>Bits/min: 209</h5>
<hr/>
<h2 id="tv-remote">TV Remote</h2>
<p class="caption">Generic TV remote</p>
<p><img alt="Generic TV remote" src="/static/bpmtvremote.png"/></p>
<p>Since TV remotes have notoriously gummy buttons, they're a lot slower than the microwave. I pressed 10 buttons in 7.13 seconds. I used a slightly different television remote than the one pictured, which is why the # of inputs may not line up. </p>
<h5># of Inputs: 42</h5>
<h5>Input Speed: 1.4/sec</h5>
<h5>TV Remote Bits/min: 453</h5>
<hr/>
<h2 id="microwave">Microwave</h2>
<p class="caption">Common Household Microwave</p>
<p><img alt="Household Microwave" src="/static/bpmmicrowave.png"/></p>
<p>Starting with common household electronics, the humble microwave. Though I've never used any button besides the numpad, start, and cancel, there are many more buttons which configure the microwave. I count 25 buttons, plus the trigger that opens the door. </p>
<p>I can hit 10 buttons in 4.5 seconds (2.2/sec), but they were all sequential and next to each other so I'll say 2 buttons/sec. Log2(26) = 4.7, 4.7 bits/input * 2 inputs/sec * 60 sec/min = 564. Evidently, I did great in high school chemistry.</p>
<h5># of Inputs: 26</h5>
<h5>Input Speed: 2/sec</h5>
<h5>Microwave Bits/min: 564</h5>
<hr/>
<h2 id="game-controller">Game Controller</h2>
<p class="caption">They control nuclear submarines with these!</p>
<p><img alt="They control nuclear submarines with these!" src="/static/bpmgamecontroller.png"/></p>
<p>We're starting to get complicated. As anyone who has played Smash knows, single button presses do very little — combinations of keys are necessary to even play. I will count single button presses, joystick positions, and a joystick position + button press. </p>
<p>There are 4 bumpers, 4 positions on the left pad (D-pad), 4 buttons on the right, and four in the center. Joysticks are analog, but we're going to discretize them as 8 edge positions and the neutral position. They also have an inward click, which I'm going to count as a button. Buttons alone, there are 18 total. There are 9 joystick positions per side, and moving between any two of them gives you 9 choose 2, or 36 unique joystick movements.</p>
<p>Combining them gets complicated. Using the left joystick, you can't press anything on the D-pad or left-center, since your thumb covers that. You can, however, still use the bumper/trigger on the left side. So for the left joystick, you have access to 12 buttons. Doing this calculation for the right joystick, you get 13 buttons to work with. Since we already counted the neutral position + buttons in the single button presses, we have 8 joystick positions. For the left and right joystick respectively, that gives us 96 and 104 combination presses. </p>
<p>I don't have a controller like this, but my friend who is a pro Smash player does. 
10 joystick moves in 2.25 seconds, averaged over 16 trials (left-right flicking, neutral to edge and back). 1.84 sec for 10 face buttons, 2.35 sec for 10 trigger buttons. Since the center buttons are harder to press, and not everyone is a competitive Smash player, I'm going to use the slower time for the button inputs. I'll use the joystick speed for the combo speed, since it'll be the limiting factor. </p>
<table class="tg">
<thead>
<tr>
<th class="tg-0lax"></th>
<th class="tg-0lax">Buttons</th>
<th class="tg-0lax">Joystick</th>
<th class="tg-0lax">Buttons+Joystick</th>
<th class="tg-0lax">Totals</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tg-0pky"># of Inputs</td>
<td class="tg-0pky">18</td>
<td class="tg-0lax">36</td>
<td class="tg-0lax">200</td>
<td class="tg-0pky">254</td>
</tr>
<tr>
<td class="tg-0pky">Input Speed</td>
<td class="tg-0pky">4.255</td>
<td class="tg-0lax">4.444</td>
<td class="tg-0lax">4.444</td>
<td class="tg-0pky">Average: 4.38</td>
</tr>
<tr>
<td class="tg-0pky">Bits/min</td>
<td class="tg-0pky">1065</td>
<td class="tg-0lax">1379</td>
<td class="tg-0lax">2038</td>
<td class="tg-0pky">4,482</td>
</tr>
</tbody>
</table>
<p><br/></p>
<h5>Game Controller Total Bits/min: 4,482</h5>
<hr/>
<h2 id="touchscreen">Touchscreen</h2>
<p class="caption">The Apple iPhone 11, most sold phone in 2019.</p>
<p><img alt="The Apple iPhone 11, most sold phone in 2019." src="/static/bpmiphone11.jpg"/></p>
<p>I'm using an Apple iPhone 11 as my reference phone since it's the most sold smartphone of 2019, according to Wikipedia. There are a lot of possible input gestures on the phone, and the touchscreen enables them to be fast. I'll count each input separately to make the count more accurate. However, I'm not counting pinches, so this will be a bit of an underestimate. </p>
<p class="caption">Icons from the navigation bar on Instagram. Green box is 100 by 75 pixels. Note how it's rectangular in the x-axis. It's much easier to press very wide, short buttons than very tall, skinny buttons on a smartphone, which I didn't realize before.</p>
<p><img alt="Icons from the navigation bar on Instagram. Green box is 100 by 75 pixels. Note how it's rectangular in the x-axis. It's much easier to press very wide, short buttons than very tall, skinny buttons on a smartphone, which I didn't realize before." src="/static/bpmphoneminsize10075.png"/></p>
<p>For smallest input size with the finger, I used the sizes of the icon set from Instagram since they're quite reliable (I don't remember ever misclicking one). These also match the icon sizes in the footer of the iPhone clock app. The green square shown is 100x75 pixels. This divides into the iPhone 11 screen resolution of 1792x828, giving us around 198 non-overlapping rectangles. </p>
<p>There are also 4 buttons around the phone, two of which can be pressed at once to trigger a different event (screenshot, shutdown, etc.). 4 choose 2 is 6, so that adds 10 inputs for a total of 208.</p>
<p>I pressed on the screen 10 times in 4 seconds. It's not noticeably faster using two fingers. I held down 20 icons on my home screen in 27.55 seconds and dragged 20 times around the screen in 17.8 seconds. Each drag is from one square to any other square, making it 330x329. That's a lot of inputs, but the log2 takes it down. And there are 4 swipes, which can be done in the center or from the edge of the screen. I did 20 swipes in 15.5 seconds. </p>
<style type="text/css">
.tg  {border-collapse:collapse;border-spacing:0;margin-left: auto; margin-right: auto;}
.tg td{border-color:white;border-style:solid;border-width:1px;font-family:Arial, sans-serif;font-size:14px;
  overflow:hidden;padding:10px 5px;word-break:normal;}
.tg th{border-color:white;border-style:solid;border-width:1px;font-family:Arial, sans-serif;font-size:14px;
  font-weight:normal;overflow:hidden;padding:10px 5px;word-break:normal;}
.tg .tg-0lax{text-align:left;vertical-align:top}
</style>
<table class="tg">
<thead>
<tr>
<th class="tg-0lax"></th>
<th class="tg-0lax">Taps/clicks</th>
<th class="tg-0lax">Hold down</th>
<th class="tg-0lax">Drags</th>
<th class="tg-0lax">Swipes</th>
<th class="tg-0lax">Totals</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tg-0lax"># of Inputs</td>
<td class="tg-0lax">208</td>
<td class="tg-0lax">198</td>
<td class="tg-0lax">39,006</td>
<td class="tg-0lax">8</td>
<td class="tg-0lax">39,420</td>
</tr>
<tr>
<td class="tg-0lax">Input Speed</td>
<td class="tg-0lax">2.5</td>
<td class="tg-0lax">0.725</td>
<td class="tg-0lax">1.12</td>
<td class="tg-0lax">1.29</td>
<td class="tg-0lax">Average: 1.41</td>
</tr>
<tr>
<td class="tg-0lax">Bits/min</td>
<td class="tg-0lax">1155</td>
<td class="tg-0lax">332</td>
<td class="tg-0lax">1025</td>
<td class="tg-0lax">232</td>
<td class="tg-0lax">2,744</td>
</tr>
</tbody>
</table>
<p><br/></p>
<h5>Touchscreen Total Bits/min: 2,744</h5>
<hr/>
<h2 id="touchscreen-stylus">Touchscreen (Stylus)</h2>
<p>Though not many smartphones these days have a stylus anymore, it does allow much more pinpoint clicks while allowing the same speed and gestures that using a finger does. I'm going to use the same speeds for taps, holds, drags, but will not count swipes or pinches since they are only possible with fingers.</p>
<p>I'll use the screen resolution we used earlier for the iPhone 11 (1792x828), and a smaller 60x60 box for the minimum pointing size. This gives us 412 potential sites for input. I'll include the volume/power/home buttons only in the taps again. </p>
<table class="tg">
<thead>
<tr>
<th class="tg-0lax"></th>
<th class="tg-0lax">Taps/clicks</th>
<th class="tg-0lax">Hold down</th>
<th class="tg-0lax">Drags</th>
<th class="tg-0lax">Totals</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tg-0lax"># of Inputs</td>
<td class="tg-0lax">422</td>
<td class="tg-0lax">412</td>
<td class="tg-0lax">169,332</td>
<td class="tg-0lax">170,166</td>
</tr>
<tr>
<td class="tg-0lax">Input Speed</td>
<td class="tg-0lax">2.5</td>
<td class="tg-0lax">0.725</td>
<td class="tg-0lax">1.12</td>
<td class="tg-0lax">Average: 1.45</td>
</tr>
<tr>
<td class="tg-0lax">Bits/min</td>
<td class="tg-0lax">1308</td>
<td class="tg-0lax">377</td>
<td class="tg-0lax">1167</td>
<td class="tg-0lax">2,852</td>
</tr>
</tbody>
</table>
<p><br/></p>
<h5>Touchscreen Stylus Total Bits/min: 2,852</h5>
<hr/>
<h2 id="touchscreen-keyboard">Touchscreen (Keyboard)</h2>
<p class="caption">Picture of the iPhone keyboard, with diction key highlighted. I didn't want to find another picture so I reused this one</p>
<p><img alt="Picture of the iPhone keyboard, with diction key highlighted. I didn't want to find another picture so I reused this one" src="/static/bpmdiction.jpg"/></p>
<p>I'm using my iPhone 6s keyboard for this one. I'll count each page of keys separately, since they're harder to get to. I'm doing phone keyboard separately because A) we have a lot of muscle memory for it, which lets us input keys a lot faster than a usual interface, and B) to double check my numbers, the final bits/min of this should be bounded by the total bits/min of the touschreen.</p>
<p>There are 29 typing keys on the primary keyboard, plus 26 for capital letters for a total of 55 keys. I type at 45 WPM on the phone, which is 225 characters/min. Including the space after ever word, that's actually 270 characters/min, or 4.5/sec.</p>
<p>On the second page, there are 25 keys (I'm not recounting the delete, return, or spacebar), which can be pressed to your heart's desire, except when the spacebar happens. Since the spacebar is essential to typing, I'm going to count my typing speed with a spacebar after every character, giving us 18 keys in 20.29 seconds. I'll also count the space after each key, so 36 keys.</p>
<p>I'll do the same for the third page, which also has 20 keys (5 are shared with page 2). I typed 19 in 20.93 seconds. Here I'll also count the space after each key, so 38 keys.</p>
<table class="tg">
<thead>
<tr>
<th class="tg-0lax"></th>
<th class="tg-0lax">First page</th>
<th class="tg-0lax">Second page</th>
<th class="tg-0lax">Third page</th>
<th class="tg-0lax">Totals</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tg-0pky"># of Inputs</td>
<td class="tg-0pky">55</td>
<td class="tg-0lax">25</td>
<td class="tg-0lax">20</td>
<td class="tg-0pky">100</td>
</tr>
<tr>
<td class="tg-0pky">Input Speed</td>
<td class="tg-0pky">4.5</td>
<td class="tg-0lax">1.77</td>
<td class="tg-0lax">1.816</td>
<td class="tg-0pky">Average: 2.7</td>
</tr>
<tr>
<td class="tg-0pky">Bits/min</td>
<td class="tg-0pky">1,560</td>
<td class="tg-0lax">460</td>
<td class="tg-0lax">471</td>
<td class="tg-0pky">2,491</td>
</tr>
</tbody>
</table>
<p><br/></p>
<h5>Touchscreen Keyboard Total Bits/min: 2,491</h5>
<hr/>
<h2 id="mousetrackpad--screen">Mouse/trackpad + Screen</h2>
<p class="caption">Macbook Air</p>
<p><img alt="Macbook Air" src="/static/bpmmacbookair.png"/></p>
<p>I'll be using my Macbook Air screen and touchpad as the reference for this one. With a screen resolution of 1440x900, it's worse than the smartphones we just saw, but I think the finger has a broader input area than the mouse pointer. For inputs, I'm counting clicks, double clicks, right clicks, drags, and swipes (scrolling included). </p>
<p>For smallest reliable click size, I'm using the geometric mean of the icons from Google Chrome and the icons on the MacOS desktop. The green square shown is 30x30 and the desktop icons on MacOS are a slightly larger 70x70, for a combined 45x45. I spend more than half my time on Chrome, and the file icons in Finder are the same size as their icons, but dragging that small an icon is difficult. </p>
<p class="caption">Smaller of the "smallest reliable click size" bounding boxes. This is from the Chrome toolbar, and has size 30x30 pixels.</p>
<p><img alt='Smaller of the "smallest reliable click size" bounding boxes. This is from the Chrome toolbar, and has size 30x30 pixels.' src="/static/bpmmouseminsize3030.png"/></p>
<p>This divides into the screen to give us 640 clickable squares, which I use for single click, double click, and right click. Drag is from any square to another, so 640x639 = 408,960. There's 2 finger swipes and three finger swipes (from any side or center of the touchpad), and a pinch for a total of 19 potential swipes.</p>
<p class="caption">Picture of the first mouse</p>
<p><img alt="Picture of the first mouse" src="/static/bpmfirstmouse.jpg"/></p>
<p>I clicked 20 times in 18.20 seconds, double clicked 20 times in 19 seconds, right clicked 18 times in 18.24 seconds, dragged 20 times in 26.15 seconds, and swiped 20 times in 20 seconds. </p>
<table class="tg" style="margin-left: auto; margin-right: auto;">
<thead>
<tr>
<th class="tg-0pky"></th>
<th class="tg-0pky">Taps/clicks</th>
<th class="tg-0lax">Double clicks</th>
<th class="tg-0lax">Right clicks</th>
<th class="tg-0pky">Drags</th>
<th class="tg-0lax">Swipes</th>
<th class="tg-0pky">Totals</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tg-0pky"># of Inputs</td>
<td class="tg-0pky">640</td>
<td class="tg-0lax">640</td>
<td class="tg-0lax">640</td>
<td class="tg-0pky">408,960</td>
<td class="tg-0lax">19</td>
<td class="tg-0pky">410,899</td>
</tr>
<tr>
<td class="tg-0pky">Input Speed</td>
<td class="tg-0pky">1.099</td>
<td class="tg-0lax">1.05</td>
<td class="tg-0lax">0.986</td>
<td class="tg-0pky">0.764</td>
<td class="tg-0lax">1.0</td>
<td class="tg-0pky">Average: 0.98</td>
</tr>
<tr>
<td class="tg-0pky">Bits/min</td>
<td class="tg-0pky">615</td>
<td class="tg-0lax">587</td>
<td class="tg-0lax">552</td>
<td class="tg-0pky">855</td>
<td class="tg-0lax">255</td>
<td class="tg-0pky">2,864</td>
</tr>
</tbody>
</table>
<p><br/></p>
<h5>Mouse+Screen Total Bits/min: 2,864</h5>
<hr/>
<h2 id="keyboard">Keyboard</h2>
<p class="caption">Physical keyboard, reduced because it doesn't have the numpad on the right.</p>
<p><img alt="Physical keyboard, reduced because it doesn't have the numpad on the right." src="/static/bpmkeyboard.jpg"/></p>
<p>I'll be using my Macbook Air keyboard, which is quite similar to the one shown above. I'm counting single keypresses for typing and not typing separately, and also combination keypresses (Control, Alt/Option, Command, Function, or Shift + any other keys).</p>
<p>Believe it or not, there are 78 keys on the keyboard! I expected way less. I can type at 80 WPM on a good day, which is 400 keys/min assuming an average word length of 5 characters a word. I'm going to count each key that actually types a symbol in a normal text editor, which is 49 keys. Since holding shift doubles each key, this makes 98 inputs at 400 keys/min. Including the spacebar after every word, that's 480 keys/min, or 8/sec.</p>
<p>That leaves 14 function keys, which I can press 20 times in 5 seconds. </p>
<p>There are 7 combination keys (separating the left and right Command and Alt/Option) which can be pressed down in any combination with any typing or nontyping key. There are 69 of those keys. It's unrealistic to expect to be able to press any combination of the keys though (Imagine holding down all 7!), so we'll cap it at 3 combo keys at most. We also have to subtract out Shift + the 49 typing keys, since we already counted those in the typing test.</p>
<p>For 1 combo key, we have 7 options. For 2, we have 21. For 3, we have 35 options. In total, that gives us 63x69=4347 options for input. I can press 20 combinations in 14 seconds. </p>
<table class="tg">
<thead>
<tr>
<th class="tg-0lax"></th>
<th class="tg-0lax">Single (typing)</th>
<th class="tg-0lax">Single (other)</th>
<th class="tg-0lax">Combo</th>
<th class="tg-0lax">Totals</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tg-0pky"># of Inputs</td>
<td class="tg-0pky">98</td>
<td class="tg-0lax">14</td>
<td class="tg-0lax">4,347</td>
<td class="tg-0pky">4,459</td>
</tr>
<tr>
<td class="tg-0pky">Input Speed</td>
<td class="tg-0pky">8</td>
<td class="tg-0lax">4</td>
<td class="tg-0lax">1.429</td>
<td class="tg-0pky">Average: 4.48</td>
</tr>
<tr>
<td class="tg-0pky">Bits/min</td>
<td class="tg-0pky">3175</td>
<td class="tg-0lax">914</td>
<td class="tg-0lax">1036</td>
<td class="tg-0pky">5,125</td>
</tr>
</tbody>
</table>
<p><br/></p>
<h5>Physical Keyboard Total Bits/min: 5,125</h5>
<hr/>
<p class="caption">Table of information transfer rates of all research devices</p>
<p><img alt="Table of information transfer rates of all research devices" src="/static/bpmITRResearch.png"/></p>
<h1 id="list-of-experimental-input-devices">List of Experimental Input Devices</h1>
<p>These are research devices that are still working out the kinks, and don't work incredibly reliably yet. I'm including voice control because it's a biometric device instead of a button input like the earlier ones, and doesn't work for everyone's voice yet.</p>
<hr/>
<h2 id="voice-control-diction">Voice Control (Diction)</h2>
<p class="caption">Voice control or diction button on an iPhone keyboard</p>
<p><img alt="Voice control or diction button on an iPhone keyboard" src="/static/bpmdiction.jpg"/></p>
<p>I used Google's voice to text function to dictate the sentence "Google's free service instantly translates words, phrases, and web pages between English and over 100 other languages." At 114 characters (118 with punctuation) in 7.37 seconds, this yields a whopping 165 WPM, or 957 characters/min. Apple's diction feature took the same amount of time, which was how long it took me to read the sentence.</p>
<p>Since voice diction cannot detect punctuation or capital letters (except at the beginning of sentences), this is out of the 10 numbers, 26 letters, and spacebar. That leaves us with 37 unique input keys. </p>
<h5># of Inputs: 37</h5>
<h5>Input Speed: 15.47 characters/sec</h5>
<h5>Bits/min: 4,835</h5>
<hr/>
<h2 id="fingerprintface-recognition">Fingerprint/Face recognition</h2>
<p>This is probably a special class of "input", but I thought it'd be cool to include. </p>
<h3>Fingerprints</h3>
<p><a href="https://www.nist.gov/news-events/news/2004/07/nist-study-shows-computerized-fingerprint-matching-highly-accurate">NIST</a> says fingerprint scanners only give false positives 0.01% of the time. Assuming this is consistent, the fingerprint scanner reliably can differentiate you and 700,000 others from everyone else on earth, which is 10,000 other groups of 700,000 people. I'll say that's 10,000 inputs. My phone takes around 1 second to scan, but sometimes fails, so I'll say it operates at 1.5/sec</p>
<h5># of Inputs: 10,000</h5>
<h5>Input Speed: 1.5/sec</h5>
<h5>Fingerprint Scanner Bits/min: 1196</h5>
<h3>Face</h3>
<p>Also from NIST, the best face recognition has a false positive rate of 0.08%, which is much higher than the fingerprint scanner. You'll be reliably recognized along with 5,600,000 others by the best face recognition, which is 1/1250 groups. It is much faster than a fingerprint scanner though, at least on the iPhone.</p>
<h5># of Inputs: 1250</h5>
<h5>Input Speed: 1/sec</h5>
<h5>Face Recognition Bits/min: 617</h5>
<hr/>
<h2 id="eye-tracking">Eye tracking</h2>
<p class="caption">Screenshot of someone using a Tobii Eye Tracker. The big blob is the predicted gaze location, which takes up about 10% of the screen</p>
<p><img alt="Screenshot of someone using a Tobii Eye Tracker. The big blob is the predicted gaze location, which takes up about 10% of the screen" src="/static/bpmtobii.png"/></p>
<p>Eye tracking uses your gaze location as input, usually to steer your cursor, but also for indicating attention. Tobii is one of the largest eye-tracking companies, selling hardware that just plugs into your computer and reports your gaze. However, it has a large inaccuracy which isn't mentioned on the website, and refuses to even mention accuracy specs for any of their products. </p>
<p>If you watch anybody using it on YouTube like <a href="https://www.youtube.com/watch?v=uM0QtujhjcA">here</a>, you'll see the large spot size which indicates its approximate confidence zone. The frame was originally 850x475 pixels, and the size of the green square is 75x60 for an error margin of about 15% (geometric mean of x and y error). </p>
<p>If this is the minimum spot size, we can do a similar calculation to the touchscreen section and find that there are around 90 differentiable squares on the screen. However, this doesn't include clicking, and the number of flicks an eye can do in a second. If I use my touchpad click, then I can look and click at 10 locations in 9.34 seconds. If I use a blink (which some eye trackers do use), then I can do 10 blinks in 6.68 seconds. </p>
<p>The newest Tobii tracker polls at around 33Hz, but has a lot of smoothing going on. I'm going to say it updates the spot at 10Hz, because I don't notice it lagging at all whenever the eye flicks from one spot to another.</p>
<h5># of Inputs: 90</h5>
<h5>Input Speed: 1.07 clicks/sec, 1.5 blinks/sec</h5>
<h5>Eye Tracker Bits/min: 417 clicking, 584 blinking</h5>
<hr/>
<h2 id="braincomputer-interfaces-eeg">Brain-computer interfaces (EEG)</h2>
<p>There's a few different ways to detect what someone's thinking of. I'll go over both the P300 and SSVEP.</p>
<h3>P300</h3>
<p>The <a href="https://en.wikipedia.org/wiki/P300_(neuroscience)">P300</a> is a signal that arises when your expectation of what you're seeing is different from what you expect it to be. These BCIs work by flashing a grid of objects (letters, labeled buttons) and removing one object each time. If you ever don't see the one you're trying to select, then your brain emits a P300. It needs a lot of averaging across many trials to be done reliably though, and it's a very unintuitive way to select things. </p>
<p>From this <a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5924393/">review paper from 2018</a>, the average bits/min of P300 BCIs is around 30. The <a href="https://pubmed.ncbi.nlm.nih.gov/25080406/">best one</a> incorporated your brain's reaction to a familiar face to help you select a target more robustly, and hit 80 bits/min with an accuracy of 81.25%. </p>
<p class="caption">A flowchart of SSVEP brain signal processing</p>
<p><img alt="A flowchart of SSVEP brain signal processing" src="/static/bpmssvep.jpg"/></p>
<h3>SSVEP</h3>
<p>The <a href="https://en.wikipedia.org/wiki/Steady_state_visually_evoked_potential">SSVEP</a> is a signal that is also evoked by visual stimulus, but in this case it's caused by repeated flashes of light. It can be reliably detected for flickering between 10 and 20Hz. To use the SSVEP as input, you create an LED display or use a monitor with many buttons on it which are each flashing at a different frequency. Then, you look at the buttons for a short window of time. The frequency spectrum of the recorded EEG signal will have a peak at the frequency of the button you were focusing on, and the computer will know which one you want to select.</p>
<p>The tradeoff here is a longer window raises accuracy but lowers the bits/min transferred, with a minimum of about 3s of data needed to detect it reliably. The best SSVEP ITR I found is from <a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5783827/">this paper</a>, where they reached a mean of 325 bits/min. I believe this is the offline ITR though, so when they actually measured people using it live they got an ITR of 199 bits/min.</p>
<h5># of Inputs: Variable</h5>
<h5>Input Speed: 2/sec (max)</h5>
<h5>BCI Bits/min: 199</h5>
<hr/>
<h1 id="discussion">Discussion</h1>
<p>The computer (trackpad + screen + keyboard) has undoubtedly the highest input rate of any device, clocking in at 7,989 bits/min. It seems that bits/min comes more from an interface which registers button presses quickly than one that offers many simultaneously available options. </p>
<p>The game controller did better than I expected, beating out the smartphone handily, but that's just on input speed. The game controller offers much less analog control than the touchscreen, and has fewer available applications. You also must remember that the game controller does not have a high-density screen built-in, and must rely on there being one available.</p>
<p>BCIs really need some work, seeing as their best technique clocks in under a thermostat. </p>
<h1 id="both-tables">Both Tables</h1>
<p class="caption">Table of information transfer rates of all common devices</p>
<p><img alt="Table of information transfer rates of all common devices" src="/static/bpmITRCommon.png"/></p>
<p class="caption">Table of information transfer rates of all research devices</p>
<p><img alt="Table of information transfer rates of all research devices" src="/static/bpmITRResearch.png"/></p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/interfacespeeds/</guid>
      <pubDate>Thu, 21 Jan 2021 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Trying out various peak detection techniques for the SSVEP</title>
      <link>https://andykong.org/blog/ssveppeakdetect/</link>
      <description>Garbage in, garbage out.</description>
      <content:encoded><![CDATA[<html><body><p>Today I tried a bunch of techniques to try and detect SSVEP peaks in long streams of EEG data. </p>
<p>My data came from three trials were all around 40 seconds long, and involved me staring at an LED blinking at a duty cycle of 50% and frequencies of 18, 35, and 65Hz. The techniques I used were (broadly): single-channel averaging (SCA), phase-rectified signal averaging (PRSA), autocorrelation (AC), and plain ol' power spectral distribution (PSD). Lots of three letter agencies. </p>
<h2 id="procedure">Procedure</h2>
<h3>Data</h3>
<p>Since each data file I had was 40 seconds, I first confirmed that using the entire length of data (after a 60Hz notch and a 5-95 bandpass filter) with each technique showed a prominent peak at the frequency of stimulation, as a baseline. If using the entire length of data didn't work, then I was really screwed trying to detect it using less data. The only data to not show a peak at the stim frequency was the 65Hz data, which makes sense because of the small signal size since frequencies past 20 begin to attenuate. The 18Hz was the largest, and the 35Hz followed.</p>
<h3>Sliding windows</h3>
<p>For practical applications, I'd only be able to record about 5 seconds of data from the EEG before the user gets tired of staring at blinking lights. I wrote a neat function that chunked the 40 second data into overlapping, 5 second data windows. The function then runs one of the above peak detection methods on the 5 second window, and records the peak before moving on to the next 5 second window. This way, I get to see how the techniques fare across all 40 seconds, but still use a realistic time window. These graphs are longitudinal, and they'll be the focus of this post. </p>
<p>We want to see a clear horizontal line at the frequency of interest (FOI) (either 18 or 35 Hz). I'm summing how many times each frequency shows up on the graph, and printing them out in order. I'm defining "SNR" as the number of times the frequency of interest shows up divided by the number of times the next, far-off frequency shows up (so 34 and 33 don't count for a 35Hz peak, but 30Hz does). Let's get started!</p>
<h2 id="experiments">Experiments</h2>
<p>I bet you're wondering what experiments I ran today! I'm glad to show you the graphs. </p>
<h3>How do SCA, PRSA, and PSD stack up to each other?</h3>
<p>The real question! Let's take a look! All peak detection was performed on a 5 sec sliding window over the entire data stream. Peaks are just frequency with the the max power between 10 and 60Hz. Shown here is the 35Hz data. Specifics of each technique are given below.</p>
<ul>
<li>
<p>PSD: uses scipy.signal.welch, default options</p>
</li>
<li>
<p>SCA: 200 sample averaging window, non-overlapping. Welch used after averaging to get PSD.</p>
</li>
<li>
<p>PRSA: 200-wide window, anchor points determined using T=2 (next two points had to be &gt; than last two points). Welch used afterwards for PSD. </p>
</li>
</ul>
<p class="caption">Graph of power spectral distribution of a 35Hz signal, fed in 5s increments and peak detected from 10-60Hz</p>
<p><img alt="Graph of power spectral distribution of a 35Hz signal, fed in 5s increments and peak detected from 10-60Hz" src="/static/PSD5secwindow.png"/></p>
<p class="caption">Graph of power spectral distribution of a single-channel averaged 35Hz signal, split in 5s increments and peak detected from 10-60Hz</p>
<p><img alt="Graph of power spectral distribution of a single-channel averaged 35Hz signal, split in 5s increments and peak detected from 10-60Hz" src="/static/SCA5secwindow.png"/></p>
<p class="caption">Graph of power spectral distribution of a PRSA 35Hz signal, split in 5s increments and peak detected from 10-60Hz</p>
<p><img alt="Graph of power spectral distribution of a PRSA 35Hz signal, split in 5s increments and peak detected from 10-60Hz" src="/static/PRSA5secwindow.png"/></p>
<p>The SNRs are as follows, though from the graph it's pretty clear which one has the best line around 35 Hz (It's the PRSA, the line is clearly there and it's not as noisy as SCA).</p>
<ul>
<li>
<p>PSD                             # SNR 2.8 at 10 sec,     1.0 at 5 sec</p>
</li>
<li>
<p>SCA        # SNR 3.7 at 10 sec,     2.0 at 5 sec</p>
</li>
<li>
<p>PRSA                         # SNR 24.3 at 10 sec,   2.8 at 5 sec</p>
</li>
</ul>
<p>Bonus - Autocorrelation + PSD (Not shown)      # SNR 1.35 at 10 sec, 1.1 at 5 sec</p>
<p>Here's the picture of the PRSA working on a 10 second window of data. It's almost a solid bar, concentrated around 35Hz. This was the best result I got.</p>
<p class="caption">Graph of power spectral distribution of a PRSA 35Hz signal, split in 10s increments and peak detected from 10-60Hz. Notice how much nicer it is than the noisy messes above.</p>
<p><img alt="Graph of power spectral distribution of a PRSA 35Hz signal, split in 10s increments and peak detected from 10-60Hz. Notice how much nicer it is than the noisy messes above." src="/static/PRSA10secwindow.png"/></p>
<h3>Does order of filtering matter for single-channel averaging?</h3>
<p>No, except for the notch filter. Here, the blue line shows filtering before averaging, and the orange shows filtering afterwards. We see that the 60Hz makes a comeback if you average after filtering, but it doesn't really affect the rest of the graph at all. </p>
<p class="caption">Single channel averaging comparison between filtering before (orange) and after (blue)</p>
<p><img alt="Single channel averaging comparison between filtering before (orange) and after (blue)" src="/static/SCAfilterordercomparison.png"/></p>
<h3>Can you use overlapping windows for single-channel averaging?</h3>
<p>No. It really heavily concentrates the frequency band you allow it to have (If you do 40 length windows at a sample rate of 200Hz, you get heavy power at ALL multiples of 5Hz, and nowhere else). Completely unusable. The power of each peak isn't even a metric for anything either. </p>
<h3>Does autocorrelation improve the ability of the other methods mentioned above, applied before or after?</h3>
<p>Not really. Usually makes it worse, but not by much. </p>
<h3>Can you use autocorrelation alone for peak detection?</h3>
<p>Nah. The autocorrelation is insanely good if you run it on long data windows (think 20+ seconds), but really terrible at windows of less than 10 seconds. </p>
<h3>What's the best solution you've found?</h3>
<p>The PRSA definitely takes the cake for best peak detection method I looked at today, but I think that they all suck for short data lengths (&lt;=5 sec). It's quite difficult to get any of them to be consistently active, especially since the SSVEP is so temporally varying anyway. </p>
<p>I think I'm going with the PRSA for now, since it appears to be sort of consistent. Maybe I can do a history-based voting system or something...</p>
<p class="caption">Comparison of SCA and PRSA for a 5 second sliding window. SCA is much noisier, and doesn't always help fill in the gaps left by PRSA</p>
<p><img alt="Comparison of SCA and PRSA for a 5 second sliding window. SCA is much noisier, and doesn't always help fill in the gaps left by PRSA" src="/static/SCAvsPRSA5secwindow.png"/></p>
<h2 id="takeaways">Takeaways</h2>
<p>These techniques work great for me for more than 10 seconds of data, but that just isn't feasible for real usage. Also it's annoying for me to look at a light for that long. </p>
<h2 id="next-steps">Next steps</h2>
<p>I want to make the PRSA run live. I need to add a peak detection history plot to my GUI.</p>
<p>I also want to try the different wavelength LEDs again. With peak detection, it might be better than just amplitude thresholding or something.</p>
<p>As far as new research goes, I want to try using the Stability Coefficient, empirical mode decomposition, and similary of background. Those are next. EMD removes noise, SC offers decent SSVEP identification accuracy at short data chunks around 1 second (voting system!), and similarity of background is close to SC. I also want to try multiple electrodes, and concat their data as if it's one stream. Then I could use PRSA and get great results from "10 seconds of data". </p>
<p>Ok! Cool, cya around.</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/ssveppeakdetect/</guid>
      <pubDate>Mon, 18 Jan 2021 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>EEG fails in the prescence of household EMI</title>
      <link>https://andykong.org/blog/EMIvsBCI/</link>
      <description>I am beginning to have doubts on the feasibility of everday BCIs...</description>
      <content:encoded><![CDATA[<html><body><p>Hello!</p>
<p>Yesterday I ran several experiments on my EEG (electroencephalography) headset. You always start with something you know is working — in this case, I checked the impedance (low, 10kΩ instead of the usally 400kΩ), then performed the closed-eye alpha spike test (check <a href="../bcieyeclosedetection">here</a> if you don't know what I'm talking about). It worked, so I moved on to trying to evoke an SSVEP with a 15Hz flashing light. I had just built this structure for blinking 3W LEDs that looks vaguely like an apartment front architecture student would have to design in a class, and it worked wonderfully. The SSVEP is evoked most strongly from 10-20Hz, with a peak around 15Hz. I used 15Hz, and the peak was as large as the closed-eye alpha waves, which are the strongest EEG signal discovered. Basically, it was working fine. </p>
<p class="caption">Picture of the apartment-shaped blinker, Arduino controlled MOSFETs and a CC source made from 1W resistors and an LM317 to drive the big LEDs.</p>
<p><img alt="Picture of the apartment-shaped blinker, Arduino controlled MOSFETs and a CC source made from 1W resistors and an LM317 to drive the big LEDs." src="/static/aptblinker.jpg"/></p>
<p>At this point my laptop began running low, so I plugged it into the wall. The next 2 hours I spent running experiments, trying new signal averaging techniques. However, none of them seemed to be working — there was this constant randomness to the signal. Since I had just checked whether the headset measured EEG properly, I thought for sure that new technique just didn't work. I made the experiments simpler and simpler, and eventually I went back to doing the closed-eye thing. Guess what? Our nice consistent graphs had tanked, and were now noisy as hell with the peak barely discernible. </p>
<p>I started considering environmental factors, like EMI emission from the things near me. I knew the apartment blinker setup didn't throw off that much EMI — I had put it across the table, and checked to make sure that the 15Hz didn't just spike all the time. Since it had worked earlier, I knew the EMI pick-up was much lower than the SSVEP signal (which is very small, under 10 microvolts). My laptop finished charging, so I unplugged it and tried my alpha test again. And it worked perfectly :(. It was the charger! </p>
<p class="caption">Still from the video "Removing RFI Noise from MacBook Power Supply
"</p>
<p><img alt='Still from the video "Removing RFI Noise from MacBook Power Supply
"' src="/static/macbookrfi.jpg"/></p>
<p>I'm no expert, but modern laptop chargers pass a huge amount of DC current from the AC wall, and usually convert it using a switching power converter instead of a linear DC converter. They're usually more efficient (80% vs 60%), which you care about when you're passing 80+ watts to charge a computer battery. However, they generate a lot of high frequency noise because they have to switch constantly, on and off, to produce a stable current. Most of my info here I read a <a href="http://www.righto.com/2015/11/macbook-charger-teardown-surprising.html">teardown by Ken Shirriff</a>, but I also found a <a href="https://t.co/cDLqXJmekF?amp=1">Youtube video</a> which shows how to reduce EMI produced by a Macbook MagSafe (though an older version). </p>
<p>Though the EEG board I'm using doesn't have a physical connection to my laptop, it did sit right next to my laptop as it charged. The Macbook grounds itself to its chassis, which means that there should be some radiated EMI from the casing itself to nearby devices (I can see this on the EEG output as increased 60Hz noise when I touch my laptop — even when it's not plugged into the wall!). Since EEG is so sensitive, I think the proximity to my charging laptop is what did my signal in ;(. Wack!</p>
<h2 id="discussion">Discussion</h2>
<p>It's fine that I have to redo the experiments, but my main concern is for the usefulness of EEG in daily settings. I mean, brain-computer interfaces are the future of HCI (in my opinion); they're useless if they can't work in the presence of everyday electrical noise.</p>
<p>I started using the SSVEP because it's a stable, high bandwidth signal, but if the alpha spike can't even function in the presence of noise then I don't know what can. </p>
<p class="caption">The alpha wave spike when your eyes close is one of the strongest EEG signals</p>
<p><img alt="The alpha wave spike when your eyes close is one of the strongest EEG signals" src="/static/emialphaspike.png"/></p>
<p>Maybe if we shielded the board it'd be slightly better, but even then the cables have a decent amount of pickup, as does my conductive, sacks-of-saltwater body. Maybe shielded cables and boards will have to be the norm? But then if a person touches the box, it'll still couple noise into the whole system. It seems intractable, considering our power systems are too ingrained to change now, and our brain signals aren't getting any stronger (In fact, if you go on Facebook, they seem to be getting weaker... /s). The future of BCI may not last very long...</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/EMIvsBCI/</guid>
      <pubDate>Sun, 17 Jan 2021 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>The first generation of handheld barcode scanners had laser tubes</title>
      <link>https://andykong.org/blog/barcodehistory/</link>
      <description>History of the LS1000 and MH290, and a demo of the 2nd!</description>
      <content:encoded><![CDATA[<html><body><p>Hi! Today we have a history lesson! I'm going to tell you the history of the handheld barcode scanner, and how to use one if you have one lying around. </p>
<h2 id="history">History</h2>
<p>All but ubiquitous today, the first barcode scanners were stationary and built into the checkout counter. This made it difficult to scan large items, which couldn't easily be passed above the desk. Though laser diodes were invented shortly after the laser (1962, 1960), they were not mass produced until the late 1980s, so these early barcode scanners had to make do with laser tubes — big, fragile, bulky things that are usually around a foot long. So how did they make them handheld?</p>
<p class="caption">Original LS1000 barcode scanner versus modern day barcode scanners. The laser tube in the LS1000 is horizontal and takes up most of the length, while the laser diode powering the 2nd is smaller than your pinky.</p>
<p><img alt="Original LS1000 barcode scanner versus modern day barcode scanners. The laser tube in the LS1000 is horizontal and takes up most of the length, while the laser diode powering the 2nd is smaller than your pinky." src="/static/barcodescannerls1000.jpg"/></p>
<p>With Symbol Technologies, Dr. Jerry Swartz performed calculations in the late 70s and believed that the laser tubes could be miniaturized to 5-6 inches, which were short enough to fit into a handheld scanner. The first company he asked to manufacture them laughed him out of the room, but the second company, Uniphase, believed it could be done. Uniphase later overtook that first company, Spectra-Physics, as the largest manufacture of HeNe laser tubes. The barcode scanner that they created with the shortened laser tube was the LS1000, pictured above. </p>
<p>Metrologic followed shortly after and released their handheld barcode scanner. They mounted the laser tube along the handle instead along the top shaft, and achieved a much more compact size. </p>
<p class="caption">The Metrologic 290 handheld barcode scanner. The laser tube is hidden in the handle</p>
<p><img alt="The Metrologic 290 handheld barcode scanner. The laser tube is hidden in the handle" src="/static/mh290.jpg"/></p>
<p>After some fundamental problems of room-temperature lasing and stable output were solved, Japan began mass-manufacture of compact laser diodes in the early 80s. Symbol began incorporating these diodes into their products, producing much smaller barcode scanners due to the lack of massive HeNe tube to produce the laser. These lead to the kinds of barcode scanners today, pictured above next to the LS1000.</p>
<h2 id="where-do-you-find-them-now">Where do you find them now?</h2>
<p>Today, these early barcode scanners are hard to find and fairly expensive when you do find them. They're all pretty bulky due to the laser tube, and fragile because of it. Nobody manufactures them anymore, so you'll have to look on resale markets — usually you can find one for $40-$100 on eBay. You can tell it has a tube because of the size — smaller, compact models can't possibly house the 5 inch laser tube that the earlier barcode scanners housed. </p>
<p>In the past, I found a bulk sale of 17 MH290s and purchased them, and am currently reselling in individual quantities on eBay <a href="https://www.ebay.com/itm/Metrologic-MH290-Barcode-Scanner-HeNe-Laser-inside/363080135269">here</a>. I think everyone who buys old barcode scanners also knows there's a laser tube inside them, because why else would you be buying such an old barcode scanner? It works the same as a new barcode scanner, just bigger and dirtier. </p>
<h2 id="what-do-you-do-with-them">What do you do with them?</h2>
<p>I guess you could open a vintage grocery if you really wanted, but I wanted the laser tube from within! While reading Sam's Laser FAQ, I found out about the MH290 for the first time and that's what started this whole obsession.</p>
<p class="caption">The laser tube from the MH290 lasing.</p>
<p><img alt="The laser tube from the MH290 lasing." src="/static/barcodescannerlaseron.png"/></p>
<p>Theoretically, <a href="http://www.repairfaq.org/sam/sale/henemll1.htm">this guide</a> for the MH290 from Sam's Laser FAQ tells you everything you need to know. Practically there's a few considerations you need to think about, like the current draw being a few amp. A 15V wall adaptor will work, and only requires a bit of soldering to get a wire to trigger the PWM-on pin on the DIP IC that drives the laser. If you use an weak power supply or wall brick which can't supply enough current, you'll notice a slight flickering and buzzing to the laser which makes it look unstable but also <em>cool</em> at the same time.</p>
<p>This is the first HeNe laser I've owned, and it's probably useful for science. Let me know if you know of anything cool I can do with one!</p>
<p><br/><br/><br/><br/></p><hr/>
<h3>References</h3>
<p>[1] <a href="http://www.repairfaq.org/sam/sale/henemll1.htm">Sam's Repair FAQ guide to powering on MH290</a></p>
<p>[2] <a href="http://www.scholarpedia.org/article/Bar_code_scanning">Scholarpedia entry for bar code scanning</a></p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/barcodehistory/</guid>
      <pubDate>Thu, 14 Jan 2021 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>My new favorite EEG technique: Phase-rectified signal averaging</title>
      <link>https://andykong.org/blog/PRSAhighlight/</link>
      <description>Basic method of aligning waves helps when averaging out noise from long time-series</description>
      <content:encoded><![CDATA[<html><body><p>Today I'm going to tell you about the technique of phase-rectified signal averaging, or PRSA, applied to EEG signals. I stumbled upon <a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.160.879&amp;rep=rep1&amp;type=pdf">this</a> paper talking about it. Originally the technique was applied to like astronomy data or something, you can find that here, <a href="https://www.sciencedirect.com/science/article/pii/S037843710501006X">"Phase-rectified signal averaging detects quasi-periodicities in non-stationary
data"</a>. This guy who used to be at Philips Research wrote it, he now works at some Mattress company's research department doing sleep EEG to determine how comfy their products are. Interesting career path.</p>
<h2 id="whats-all-this-prsa-stuff-anyway">What's all this PRSA stuff anyway?</h2>
<p>Anyway, onto the good stuff. Basically, sometimes your SSVEP signal is dispersed along a time series — maybe it's not always coherent, and it comes and goes and the phase is not directly sequential. When you average your data, you find that a lot of the signal was overlapping, and not in the good way. It averages to zero, and your signal is worse than the noise previously was. </p>
<p><img alt="Graphs from original PRSA paper" src="/static/prsaoriginal.png"/></p>
<p>This technique relies on the waveform <em>usually</em> lining up within the noisy signal. Which is to say, when the waveform of interest goes up, the noise+signal overall should go up too, at least for a majority of the times you check it. So you average a big window around all "up" or "down" points, and might be able to pull out the original signal. The original paper uses some jank original signal, so it looks atrocious even after averaging. I'm told this was on purpose. Why couldn't they just use a sine wave?</p>
<p>I can't wait to try it. Looks like it won't work, but it seems stupid enough that it just might.</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/PRSAhighlight/</guid>
      <pubDate>Wed, 13 Jan 2021 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Preliminary 5W LED Tests</title>
      <link>https://andykong.org/blog/5WLEDprelimtests/</link>
      <description>Projecting 5W of light onto a wall — DIY Projector</description>
      <content:encoded><![CDATA[<html><body><p>I show you tests of some 5W LEDs I bought off Amazon, and how they look projected onto a wall that's far away.</p>
<h2 id="setup">Setup</h2>
<p>I got 10 of these yellow LEDs with legs in the mail, along with some aluminum backing plates. It's inordinately hard to find high power LEDs with the backing plates attached, but they're so cheap I just caved and bought them separately. </p>
<p class="caption">Setup of the 5W LEDs. Each LED has a drop of 6-7V, so I am using two in series to test them with my 12V power supply</p>
<p><img alt="Setup of the 5W LEDs. Each LED has a drop of 6-7V, so I am using two in series to test them with my 12V power supply, which is just a laptop wall brick." src="/static/5Watters.jpg"/></p>
<p>I'm using a laptop wall brick, which outputs 5V and 12V at 1.5A each. Kinda cool, high output current bricks are a little hard to find. I always feel bad cutting the plug to get to the wires too... </p>
<p>Anyway. I soldered on the two LEDs, no thermal paste just mechanical contact for now. I'll probably mount it on a larger heatsink, because this thing gets HOT. The circular indents around the edge look like they're for screws to hold in place. </p>
<h2 id="projection">Projection</h2>
<p>You remember those <a href="../fresnellens">Fresnel lenses</a> I bought a while back? This is what they were for. Here I got my brother to hold the sheet above the LEDs, and as he moved it up and down, the projected spot on the ceiling changed size and brightness. </p>
<p class="caption">5W LED (very bright) being held under a Fresnel lens from my last post. I'm holding by the alligator clip since it's very hot.</p>
<p><img alt="5W LED (very bright) being held under a Fresnel lens from my last post. I'm holding by the alligator clip since it's very hot." src="/static/5W_setup.jpg"/></p>
<p class="caption">Far from the LEDs</p>
<p><img alt="Fresnel lens held far" src="/static/5W_tightcircles.jpg"/></p>
<p class="caption">Middle far</p>
<p><img alt="Fresnel lens held in the middle" src="/static/5W_circles.jpg"/></p>
<p class="caption">Close to the LEDs</p>
<p><img alt="Fresnel lens held close" src="/static/5W_bigcircle.jpg"/></p>
<p>We only turned them on for about a minute, but the backplate hurt immediately to the touch. Definitely need to heatsink them, and I may even have some thermal paste from a computer build a while ago.</p>
<p>The projected spot is massive just from the floor to ceiling, not to mention one wall to another. It could be even brighter if I funnel the light using some aluminum foil or some mirrors. </p>
<p>One problem is that the Fresnel lens has distortion issues, and doesn't focus very well at all. The edges of shadows projected are all blurry, probably partially because of diffraction around my finger. Maybe I should collimate the light somehow before I shine it through the screen (I want this to show text eventually), but we'll see what problems I run into with the lens I currently have before I come up with new solutions.</p>
<p>Hopefully it'll be alright. Cya next time!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/5WLEDprelimtests/</guid>
      <pubDate>Wed, 06 Jan 2021 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Fresnel Lens</title>
      <link>https://andykong.org/blog/fresnellens/</link>
      <description>Making flat lenses using special surface ridging</description>
      <content:encoded><![CDATA[<html><body><p>Hi! Today I'm going to tell you a bit about these really cool lenses that use angled surfaces to achieve magnification and other lens effects in a flat form factor instead of a curved one. I also show you some big ones I bought, and what they're useful for. We're talking about <a href="https://en.wikipedia.org/wiki/Fresnel_lens">Fresnel Lenses</a>!</p>
<p class="caption">Fresnel lens versus regular convex lens</p>
<p><img alt="Caption" src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e8/Fresnel_lens.svg/242px-Fresnel_lens.svg.png"/></p>
<h2 id="lighthouses-and-firemaking">Lighthouses and Firemaking</h2>
<p>They were originally created for use in lighthouses, avoiding the bulky size required by normal lenses. My first experience of one was a credit card-sized one, which my friend kept in his wallet in case he was stranded and needed to start a fire. Even though the day was sunny and we found dry kindling, the low-powered lens had difficulty lighting our kindling. It burnt the loose fibers easily, but created no flame.</p>
<p class="caption">Fresnel lens in the shape of a credit card, for easy carrying around in the wallet</p>
<p><img alt="Fresnel lens in the shape of a credit card, for easy carrying around in the wallet" src="/static/fresnel_creditcard.jpg"/></p>
<h3>Projection and Magnification</h3>
<p>I needed a lens for a project, and I stumbled upon an Amazon item that was just a big flat sheet Fresnel lens for like $3. It should have all the same properties as a normal lens, just in a flat package, so I bought it immediately. Anyway, here it is. </p>
<p class="caption">Fresnel lens I bought magnifying office items</p>
<p><img alt="Fresnel lens I bought magnifying office items" src="/static/fresnel_magnify.jpg"/></p>
<p>As you can see, it's like a normal sheet of printer paper in size, and works quite well as a magnifier. I think this specific one is used to help old people read better. </p>
<p>There's a few distortion effects since the sheet isn't rigid, but those can be worked out by holding it better. The instructions provided also state that the lens works better from one side than the other, which to me seems to violate <a href="https://en.wikipedia.org/wiki/Helmholtz_reciprocity">Helmholtz Reciprocity</a>. This says that the start and end of a light ray can be reversed without any effect — sending a ray down its start angle will always lead to the end, and sending the ray up its ending angle will always go back to its start. But what do I know? Maybe it's because the ridges are only on one side of the lens. </p>
<p>The lens also works for projecting light sources. Here I am holding it up to my ceiling light, and it creates a mirror image of it quite well. </p>
<p class="caption">Fresnel lens projecting an image of my ceiling lamp onto the wall</p>
<p><img alt="Fresnel lens projecting an image of my ceiling lamp onto the wall" src="/static/fresnel_reflectproject.jpg"/></p>
<p>I'm going to use this to try and make a wall projection. Cya!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/fresnellens/</guid>
      <pubDate>Tue, 05 Jan 2021 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Hello World for Brain-Computer Interfaces</title>
      <link>https://andykong.org/blog/bcieyeclosedetection/</link>
      <description>Detecting alpha wave activity with EEG to predict whether my eyes are open or closed</description>
      <content:encoded><![CDATA[<html><body><p>Hello! I'm gonna tell you everything I know about the most consistently evoked signal in all of EEG and how to detect it — the closed-eye <a href="https://en.wikipedia.org/wiki/Alpha_wave">alpha wave</a> spike.</p>
<blockquote class="twitter-tweet tw-align-center"><p dir="ltr" lang="en">Detecting whether eyes are open/closed using <a href="https://twitter.com/hashtag/EEG?src=hash&amp;ref_src=twsrc%5Etfw">#EEG</a> alpha wave activity<a href="https://t.co/OnwhTsJSsa">pic.twitter.com/OnwhTsJSsa</a></p>— Andy (@redlightguru) <a href="https://twitter.com/redlightguru/status/1344538994235875328?ref_src=twsrc%5Etfw">December 31, 2020</a></blockquote>
<script async="" charset="utf-8" src="https://platform.twitter.com/widgets.js"></script>
<p><br/></p>
<p>This alpha wave signal is used to confirm that EEG electrodes are set up to sample brainwaves properly, and not just sampling the atmosphere's noise (which unfortunately looks a lot like passive brainwave activity).</p>
<p>Because the spike in alpha wave activity happens when your eyes are closed, this makes personally seeing it rather hard — you have to do a screen record or otherwise plot the amplitude to be able to see the spike after you've opened your eyes.</p>
<h2 id="experimental-setup">Experimental Setup</h2>
<p>Data are being recorded on a single channel of the OpenBCI Ganglion EEG board. The electrode placements are as follows: GND on right earlobe, Channel 1 (-) on left earlobe, and Channel 1 (+) on position Oz of the 10-20 international system. </p>
<p>Oz is right over the occipital region of the brain, near the bump on the middle-back of your head (this is called the inion). Sample rate is 200Hz, safety from being shocked in the brain by mains insured by the board streaming samples to my laptop over Bluetooth.</p>
<p class="caption">Picture of the Ganglion board, with two earlobe electrodes and the scalp electrode. It's spiky to go through hair</p>
<p><img alt="Picture of the Ganglion board, with two earlobe electrodes and the scalp electrode. It's spiky to go through hair" src="/static/alphawaves_electrodes.jpg"/></p>
<p>Once streamed onto the computer over BrainFlow (I made <a href="../workingwithbrainflow1" target="_blank">another blog post</a> about that), the data are filtered using SciPy's IIR filters. Mainly, a 60Hz notch filter and 5-75Hz bandpass filter, with 40dB attenuation for the stopband. These are applied with the <code>scipy.signal.filtfilt()</code> function. I don't know if the dB number is like power (div by 20 then take the logarithm) or normal (div by 10 then take the logarithm), but the stopband is completely flat either way so I don't really mind. </p>
<p>FFT is calculated using <a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.welch.html">Welch's method</a>. Alpha band integration is as simple as adding up all the magnitude contributions from frequencies in the band 7-13Hz, but Brainflow has a built-in function for doing this (if you use this, make sure you feed in the tuple as (power, freqs) instead of the (freqs, power) that <code>scipy.signal.welch()</code> gives you). That's it!</p>
<p>Implementation: I'm creating a live plot using a matplotlib animation. The indicator is drawn on-graph as both text and an emoji. </p>
<h2 id="results">Results</h2>
<p>Eyes closed are pretty reliably detected by a spike, with a lag of about a second. Eyes opening, the alpha waves drop about as fast as they rise. This is probably due to my EEG data window being 5 seconds, since I'm not doing any averaging that would otherwise slow it down. </p>
<p class="caption">Graph of baseline alpha wave activity</p>
<p><img alt="Graph of baseline alpha wave activity" src="/static/alphawaves_eyesopen.png"/></p>
<p class="caption">Vastly increased alpha wave activity when user's eyes are closed</p>
<p><img alt="Vastly increased alpha wave activity when user's eyes are closed" src="/static/alphawaves_eyesclosed.png"/></p>
<h2 id="reliability-problems">Reliability problems</h2>
<p><em>Within session</em>, I had no problems with reliability. I saw my average alpha activity around 8 uV^2, and it went above 12 or so with my eyes closed so I set that as the breakpoint. However, alpha activity also decreases with increase in drowsiness, and I finished this pretty late last night, so it could not work for me right now (not drowsy) the way the breakpoints are set.</p>
<p>I believe the baseline alpha activity varies from person to person, so the threshold would probably have to be adjusted for new people. </p>
<p>I think that's it for now. Follow me on <a href="https://twitter.com/redlightguru">Twitter</a>!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/bcieyeclosedetection/</guid>
      <pubDate>Thu, 31 Dec 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Working with BrainFlow pt. 1</title>
      <link>https://andykong.org/blog/workingwithbrainflow1/</link>
      <description>A new way to stream OpenBCI data to Python</description>
      <content:encoded><![CDATA[<html><body><p>TL;DR: The OpenBCI GUI sucks, I show you how to stream <a href="https://shop.openbci.com/products/ganglion-board">Ganglion</a> data to Python through BrainFlow to make your own GUI. </p>
<hr/>
<h3>Why would you want to make your own?</h3>
<p>For whatever reason, the OpenBCI GUI feels clunky to use, and has bad digital filtering. So aesthetics. Also, when I reimplemented the filters in Python Scipy, I got much better FFT peaks, SNR, etc. I wanted to make my own version of the GUI so I could get useful results out of it. </p>
<p>A month ago, the website said to use a combination of pySerial and their own library, pyOpenBCI. I didn't do it at this time because it looked complicated. I went back today and realized that they deprecated this old guide. Hurrah!</p>
<h3>BrainFlow</h3>
<p>They migrated over to this thing called <a href="https://brainflow.readthedocs.io/en/stable/Examples.html#python-get-data-from-a-board">BrainFlow</a>, which had a lot of setup code but only required one library. BrainFlow also offers filtering, and some other stuff that scipy doesn't for 1D data, like wavelet denoising. Now, personally I don't understand wavelet denoising at all, so I am going to do some reading before using that. However, this library is a good thing. It seems like a lot of cheaper hobbyist headsets are already on this platform, and that makes it all the easier to use. Their examples are also excellent, and work (sort-of)</p>
<p>Today, I set up the GUI (which is really just a live plot), and compared the BrainFlow FFT to Scipy's. </p>
<h3>GUI Setup</h3>
<p>I wrangled some matplotlib plots and added them to an animation so they'd update live. Turns out calling <code>fig.tight_layout()</code> takes like 80ms, which really slowed down my plotting time (10FPS tops). However, if you take out <code>fig.tight_layout()</code> from your animation loop, the plot axes start drawing over themselves! </p>
<p class="caption">Plot axes overlapping, making your plot an illegible mess from hell</p>
<p><img alt="Plot axes overlapping, making your plot an illegible mess from hell" src="/static/brainflowgraphoverwriting.png"/></p>
<p>Turns out the <code>tight_layout()</code> call notifies the figure that the background is stale (literally a boolean attribute of <code>matplotlib.Figure</code> called <code>stale</code>) and needs to be redrawn. I had left blit=True because I wanted good speed, and I guess that makes the background not redraw every loop. IDK, I read the code and it still didn't make sense. </p>
<p>I solved this by calling <code>fig.set_visible(True)</code>, which conveniently turns <code>stale=True</code> and fixes the background. Tada! Now I'm at 50Hz update rate on the graph. Excellent. Now let's see how the filters do</p>
<h3>Brainflow params</h3>
<p>BrainFlow requires a <code>BrainFlowInputParams()</code> params object to accompany the board id when initializing the "BrainShim" object. This params doesn't need much, but it does need the name of your serial port that your Bluetooth dongle is connected to. This might be hard to find, but I found a one-liner you could run to get the serial port's names as strings. <a href="https://stackoverflow.com/questions/12090503/listing-available-com-ports-with-python">SO post here</a>, but the terminal one-liner is </p>
<p><code>python3 -m "import serial.tools.list_ports; print([comport.device for comport in serial.tools.list_ports.comports()])"</code></p>
<p>Works if your base Python has serial pip installed.</p>
<h3>FFT fightoff: BrainFlow vs. Scipy</h3>
<p>I started off just comparing the FFTs. I used a window of 256, an overlap of 128, and a Blackman Harris window (Scipy's was 256, but I feel like I used that wrong...). The results were.... complicated. </p>
<p>Sometimes the peaks were comparable, but Scipy's would be much clearer among the taller peaks of noise</p>
<p class="caption">BrainFlow vs. Scipy PSD/FFT using Welch's method</p>
<p><img alt="BrainFlow vs. Scipy PSD/FFT using Welch's method" src="/static/brainflowvsscipy3.png"/></p>
<p>Other times, the BrainFlow plot would have higher peaks for some frequencies, and lower peaks for others. </p>
<p class="caption">BrainFlow vs. Scipy PSD/FFT using Welch's method</p>
<p><img alt="BrainFlow vs. Scipy PSD/FFT using Welch's method" src="/static/brainflowvsscipy4.png"/></p>
<p>I think Scipy's graphs looked cleaner, even if they were a little noisier. The peaks were higher, which is what I really wanted. I would've liked to quantitatively chosen, but since I just sampled the air, I couldn't get SNR of anything since there was no signal to compare to. Oh well. It's small dice anyway, considering if the signal is awfully noisy we can't get anything out anyway.</p>
<p>Until next time!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/workingwithbrainflow1/</guid>
      <pubDate>Wed, 30 Dec 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Testing out some high power LEDs</title>
      <link>https://andykong.org/blog/LEDs3W6piece/</link>
      <description>Casting light on far away surfaces using 3W LEDs</description>
      <content:encoded><![CDATA[<html><body><p>I bought some cheap, high-power LEDs off Amazon about a week ago. I wanted something really bright that came with a backing, and I didn't want too many. They look like this:</p>
<p class="caption">Cheap, 3W LEDs with aluminum backing connected to a 9V wall adaptor</p>
<p><img alt="Cheap, 3W LEDs with aluminum backing connected to a 9V wall adaptor" src="/static/LED3W_poweringsetup.JPG"/></p>
<p>I have here soldered it to a 9V wall adaptor, which outputs a DC 9V at 0.3A max load. This is precisely what was specified by the Amazon page, though the accuracy of that I cannot be sure. I tried a 5V at first and that failed. VERY BRIGHT! Hurts to look at for any amount of time, at arm's length. Heats up negligibly when turned on for under a minute.</p>
<p>I funneled their light through my projector lens, but just got something like this. I guess I'll need a diffuser or something between the LEDs and lens to get a uniform wall projection.</p>
<p class="caption">Projected image produced by the LEDs and a lens from a projector clock. All 6 LED segments are separated out, instead of diffused</p>
<p><img alt="Projected image produced by the LEDs and a lens from a projector clock. All 6 LED segments are separated out, instead of diffused." src="/static/LED3W_projected.JPG"/></p>
<p>I tried to diode test them using my trusty multimeter, but it saturated. I then tried using a 2V voltage reading across one LED, and it saturated as well! The voltage drop for these babies was 2.8V each!! I've never seen them so high before, though it's probably to do with needing a lot of current to turn on the LED. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/LEDs3W6piece/</guid>
      <pubDate>Mon, 28 Dec 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Easy Button Hack Pt. 1</title>
      <link>https://andykong.org/blog/easybutton1/</link>
      <description>Learning about SD cards, transistor audio amps, Arduino sleep and interrupts</description>
      <content:encoded><![CDATA[<html><body><p>WOOO. In sort-of order, today I learned: how to read from SD cards, how to play wavs from SD cards, how to boost speaker volume using a transistor, how to limit transistor current using a resistor, how to trigger interrupts for Arduino sleep modes, how to change what shape causes an interrupt, which pins are and AREN'T interrupt pins, how to hijack the button on an Easy button (not easy, actually).</p>
<p>Here's the end result: An Arduino rig that powers off until the button is pressed, then plays a random sound file from the SD card before powering off again. Gonna solder everything tomorrow and move it to battery power. </p>
<p><img alt="Pictured are the audio amplifier, speaker, Arduino Uno, and SD card" src="/static/easybuttonday1.jpg"/></p>
<p class="caption">Pictured are the audio amplifier, speaker, Arduino Uno, and SD card</p>
<p><br/></p><hr/>
<h3>Things I learned today that each took me at least an hour to figure out</h3>
<ul>
<li>SD cards in Arduino can only be a certain length and all caps, called 8.3 format ('YESYESY.WAV' for example)</li>
<li>Arduinos only have 2 interrupt pins (2, 3), the other ones don't work for hardware interrupts. </li>
<li>Transistors require a resistor in-line with the emitter (V+), and make a decent audio amplifier.</li>
<li>Random library does a clever initialization of seed using analogRead(A0) or another random analog pin. Otherwise it'll be the same set of numbers every time (for me, 1 1 2 4 5).</li>
</ul>
<h4>Nifty stuff that I thought you'd like to know</h4>
<ul>
<li>Internal pullup resistors have variable resistance, around 10-50kΩ or so. </li>
<li>The UNO is pin-by-pin compatible with the Nano. </li>
</ul></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/easybutton1/</guid>
      <pubDate>Thu, 24 Dec 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Improving noisy EEG data through single channel averaging</title>
      <link>https://andykong.org/blog/eegsinglechannelavg/</link>
      <description>How to get rid of Gaussian noise using a single electrode's measurements</description>
      <content:encoded><![CDATA[<html><body><p>So I'm working with some EEG data, right? The kind collected by these caps. I'm gonna show you how to improve your signal-to-noise ratio from single channel EEG, specifically for detecting continuously repeating weak signals.</p>
<p class="caption">Traditional EEG cap</p>
<p><img alt="Traditional EEG cap" src="/static/tradeeg.png"/></p>
<h2 id="background-you-need-to-know-about-eeg">Background you need to know about EEG</h2>
<p>Skip this part if you already know about notch filters at 50/60 Hz -.-</p>
<p>EEG (electroencephalography) is a technique for measuring brainwaves emanating from the firing of thousands of neurons under your skull. When used noninvasively, it measures the clumped firings by taking a voltage potential measurement on the surface of the skull. We can use some of these brainwaves that are predictable (or unpredictable) to control devices. These signals, or potentials, are the first brain-computer interfaces. </p>
<h2 id="traditional-eeg-noise-reduction-techniques">Traditional EEG noise reduction techniques</h2>
<p>All EEG data is <a href="https://en.wikipedia.org/wiki/P300_(neuroscience)">super noisy</a>, and I'm not just saying that to excuse the fact that <em>my data</em> is super noisy. People solve this by using great analog electrical engineering or by averaging a bunch of different sites on the head. Think about it; if the noise is Gaussian random, and you measure the same spot on the head for the same signal 100 times and average all of them together, then the noise goes down by a factor of 10! Amazing!</p>
<p>Unfortunately, electrodes take linear time to put on, and it's difficult to find 100 spots for electrodes on your head at all, much less at the same spot. So what happens instead? Well, we can repeat a stimulus a few times, and average <em>those</em>.</p>
<p>If the stimulus is fast enough, we get to do this a few 10s of times in a few seconds, and reduce our noise by a decent amount. Combining this with multiple amplifiers, and you get almost the same thing as 10x decrease in noise.</p>
<h2 id="whats-the-catch">What's the catch?</h2>
<p>The catch is that this technique is usually only applied to triggered potentials like the P300 or increase in alpha wave activity. When the user <em>activates</em> a stimulus, like seeing a card or closing their eyes, the trial starts. Then, averaging a few trials, the data becomes usable. This takes forever — a minute or so, and is COMPLETELY UNUSABLE in the real world. </p>
<p>It's also difficult to do with just one channel, for the reasons mentioned above. Multiple channels just makes it easier</p>
<hr/>
<h2 id="my-use-case">My use case</h2>
<p>OK, I admit it. I'm trying to detect the SSVEP, or the steady-state visually evoked potential. Read more about it <a href="https://en.wikipedia.org/wiki/Steady_state_visually_evoked_potential">here</a>. Basically, if you look at a light blinking from 10-20Hz, there's a very clear peak at that frequency in the EEG data measured over your visual cortex. </p>
<p>However, it's a continuous signal, so I can't "time" when it starts and stops. I'm trying to do single channel SSVEP detection, instead of multi-channel. And, this signal is weak above 15Hz, and I'm looking for it at 35Hz. That's the opposite of most traits that make it easier to find. </p>
<p>I'll show you my data. I'm looking for the 35Hz signal content of this brainwave. Top graph shows the waveform, measured over 40 sec, and bottom graph shows the power spectral density of the top graph (Basically the Fourier transform).</p>
<p class="caption">35Hz signal from EEG DROWNED in noise</p>
<p><img alt="35Hz signal from EEG DROWNED in noise" src="/static/35_full_noavg.png"/></p>
<p>You can barely see the peak at all, much less differentiate it from any adjacent peak! I mean, it's high, but it's not that high, and it is surrounded by taller brothers. </p>
<p>What if I told you there was a way I could make that graph into this one?</p>
<p class="caption">35Hz signal from EEG clear as day</p>
<p><img alt="35Hz signal from EEG clear as day" src="/static/35_full_04window.png"/></p>
<p>That's a much nicer looking peak, isn't it? </p>
<h2 id="the-catch-pt-2">The catch pt 2</h2>
<p>I'm using 40 seconds of data here. NO WAY anyone sits still for 40 sec allowing you to collect their brainwaves to control something. But I'm going to show you that this still works with a much smaller window of 3 seconds, which I think makes a usable BCI</p>
<hr/>
<h2 id="how-to-do-single-channel-noise-averaging">How to do single channel noise averaging</h2>
<p>This technique takes advantage of the fact that a single channel of this EEG data oscillating at 35Hz repeats itself every second. Well, it repeats itself every 1/35 second to be more exact. What we can do is average over a window that contains an integer multiple of full waveforms (1/5 sec, 1/7 sec, 1/35 sec, 1 sec), and if the signal exists at all it will be amplified, and any other signal will be destroyed. Then we can take the FFT of it, and figure out what frequency we're looking for.</p>
<p>Here's an example using a shorter chunk of data, 3 seconds. We see that with no averaging, the 35 peak rivals the 25Hz peak, and both are dominated by the &lt;20Hz peaks. </p>
<p class="caption">A raw 3 second clip from our wave shows no 35Hz prominence</p>
<p><img alt="A raw 3 second clip from our wave shows no 35Hz prominence" src="/static/35_3sec_noavg.png"/></p>
<p>However, with averaging, the 35Hz peak is much higher than the 25Hz. While still lower than the high peak, we can just constrain the search space to &gt;20Hz frequencies, and this will give us the answer we want. What else can we do to make ths more apparent?</p>
<p class="caption">An averaged 3 second clip from our wave shows slightly higher 35Hz peak, but nothing amazing</p>
<p><img alt="An averaged 3 second clip from our wave shows slightly higher 35Hz peak, but nothing amazing" src="/static/35_3sec_06window.png"/></p>
<p>Actually, to fix this, you can increase the FFT resolution (not really because of math or whatever it's really just sine interpolation but it looks like it, okay?) by padding the data with zeros! </p>
<p class="caption">Animation of the FFT resolution increasing in front of our very eyes by using zero-padding</p>
<p><img alt="Animation of the FFT resolution increasing in front of our very eyes by using zero-padding" src="/static/padright.gif"/></p>
<p>if you change the window size and pad with zeros on both sides, the signal becomes very nicely defined, and the 35Hz is almost trivial to detect.</p>
<p class="caption">Well-isolated 35Hz signal from our previously noisy-as-hell data using single channel averaging and zero-padding</p>
<p><img alt="Well-isolated 35Hz signal from our previously noisy-as-hell data using single channel averaging and zero-padding" src="/static/35_padboth2.png"/></p>
<h3>Caveats pt 3???</h3>
<p>These are not cherrypicked examples, they're just the first 3 seconds of my collected data. </p>
<p>I tried this with other starting positions and it is a bit finicky (all BCI stuff is), but the principle is still sound. For me, if one section didn't work, an adjacent one usually did. </p>
<p>One problem is other multiple of 5 frequencies also compound when your window is a multiple of that size (I used 0.2 and 0.6 sec windows mostly), though the zero padding helps with that.</p>
<h3>Conclusion</h3>
<p>I haven't seen this mentioned in the literature (if you had, please email it to me!). Since the SSVEP is so weak at higher frequencies but the number of waveforms appears more often, this should make high-frequency SSVEP detection much easier. Anyway, I hope you found this useful, and/or interesting. If so, smash that like button. Cya!!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/eegsinglechannelavg/</guid>
      <pubDate>Fri, 18 Dec 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Coin vibration motors arrived!</title>
      <link>https://andykong.org/blog/coinvibrationmotor/</link>
      <description>Now I can do "haptic feedback"</description>
      <content:encoded><![CDATA[<html><body><p>We were going over a <a href="https://la.disneyresearch.com/publication/surround-haptics-sending-shivers-down-your-spine/">paper by Disney Research</a> in class last week that said two sufficiently close vibration motors on someone's skin created a sensation in a single spot in between them. Then, you could just vary the power to one or the other and "move" the phantom dot back and forth between the two, essentially creating a vibrating line for the price of two motors!</p>
<p class="caption">Image from the Disney Paper "Surround Haptics: Sending Shivers Down Your Spine"</p>
<p><img alt='Image from the Disney Paper "Surround Haptics: Sending Shivers Down Your Spine"' src="/static/surroundhaptics.png"/></p>
<p>This even extends to two 'virtual' spots, which can create a second phantom vibration spot in their centroid. How cool!</p>
<h3>I had to try it!</h3>
<p>So I ordered these tiny coin cell haptics motors off Amazon. 10 for $6, smaller than my pinky fingernail, and powered off 3 volts. Adhesive side and a foamy side. Nominal resistance is 36Ω, I powered them off my Arduino's 3.3V and it worked well. Not insane vibration, but very noticeable! </p>
<p>The thing started warming up pretty quickly, so I may have to deal with that, but it could just be a lack of current limiting hardware in the way. It also might self-regulate when it gets hot enough, but I don't want it strapped to my arm when I find that out. I'm probably pulling too much current from the Arduino as-is. </p>
<p class="caption">Me holding one of the tiny coin vibration motors I ordered, foamy side up</p>
<p><img alt="Me holding one of the tiny coin vibration motors I ordered, foamy side up" src="/static/coinvibrationmotors.png"/></p>
<p>I'm gonna try it and let you know how it goes! Be safe!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/coinvibrationmotor/</guid>
      <pubDate>Mon, 14 Dec 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Playing sounds with an Arduino, speaker, and no amplifier</title>
      <link>https://andykong.org/blog/arduinodirect2wav/</link>
      <description>LM386 audio amplifier? I 'ardly know her!</description>
      <content:encoded><![CDATA[<html><body><p>Today in Being Lazy With Andy, I'm gonna show you how to play audio directly from a microcontroller's output pins — no op amp, no transistor.</p>
<p class="caption">LED for kicks</p>
<p><img alt="LED for kicks" src="/static/direct2wav_fullsetup.jpg"/></p>
<h2 id="context">Context</h2>
<p>I'm currently doing an Easy button hack-apart, which I'll showcase later. As an intermediate step, I wanted to play custom sounds from the speaker. I don't have any audio amplifiers lying around, and besides, there's not a ton of room in the button itself to fit that. I will probably end up using a transistor, but at the moment I just wanted to test the feasability of playing audio without anything except the speaker and Arduino. The results are useable — loud enough to be heard in a bedroom and passable for voices.</p>
<p>It makes sense for a microcontroller to play WAVs — they're already just pressure levels, so you should be able to play each pressure level as a tone with a set time. Luckily many people have thought of this, and have written lovely libraries for it. Primarily, this relies on some Arduino Playground code written by Michael Smith, and the lovely tutorial by <a href="http://highlowtech.org/?p=1963">High-Low Tech Group</a> at MIT Media Lab. You can also find this library from Arduino's IDE directly by searching PCM Audio in the Tools-&gt;Manage Libraries... window. </p>
<p class="caption">PCM Library at the click of a button</p>
<p><img alt="PCM Library at the click of a button" src="/static/direct2wav_library.png"/></p>
<h2 id="basic-setup-instructions">Basic Setup Instructions</h2>
<p>Hardware-wise you need to connect the GND to GND, and pin 11 of your Arduino to the V+ of the speaker. I actually think the speaker is voltage polarity agnostic, so either direction should work. I also added a 330Ω resistor in series with the speaker to limit the current draw from the Arduino's pins. I think their max safe draw is 20mA, and this speaker is probably 8Ω and would draw 5V/8Ω = 625mA at max output. With the resistor, this is limited to 5V/338 ≈ 15 mA. </p>
<p>For the software, the starter code from high-low tech should boot directly and work. When it starts, you should hear a lady's voice say "Arduino Demilvinove" or however you spell it. </p>
<h3>What about custom audio?</h3>
<p>I'm glad you asked! This was the fun part. Their example shows a long array of byte values, centered on 128 as their zero point. They recommend that you use an 8-bit, 8000Hz (8kHz) sample rate, mono channel WAV file. What you need to do is record some audio, then convert it into a .wav with those specific settings. You can do this conversion online, or maybe you already have a local program that can do this. </p>
<p>Once you have your .wav, you'll need to get the byte values onto your clipboard somehow to paste into the PROGMEM sample array from the example, which is nontrivial because the .wav will want to copy as char codes instead of actual byte values.</p>
<h2 id="how-i-did-it">How I did it</h2>
<p>I used QuickTime to record some brief audio, and then converted the .aifc file to .wav using <a href="https://audio.online-convert.com/convert/aifc-to-wav">this site</a>, which is handy because it has many of the downsampling options I wanted. </p>
<p>I then tried to use their Processing script to copy a .wav to my clipboard, but could't run it because it was outdated. I tried to modify it in Processing to compile, but for some reason it wouldn't parse the .wav properly :(. Instead, I found <a href="https://guilhermerodrigues680.github.io/wav2c-online/">this web version</a> of wav2c. Turns out lots of people have solved this problem, it's just very difficult to find a web version that will do it for you in JS. </p>
<p>Anyway, it gives you a nice long text of values you can copy and paste into your code. I would like this better if it were all on one line, but that might be a personal preference. </p>
<p><img alt="Image Caption" src="/static/wav2c_online.png"/></p>
<p>Paste it in the example code, comment out or delete the old line with all the values, and hit start! Your Arduino should play your sound file!</p>
<h1 id="additional-things-you-can-try">Additional things you can try</h1>
<h3>Optimizing for voice</h3>
<p>I'm encoding voice files specifically, so I figured I could increase the contrast in the file to get better volume. I iterated through in Python and found the largest offset from 128, then multiplied all samples' differences with 128 by some large constant and added them back. So something like <code>[128, 130, 120]</code> (diff from 128 is <code>[0, 2, -8]</code>) becomes <code>[128, 138, 88]</code> after a 5x contrast increase. I think I'm just reinventing the wheel here, but this slight modification made my sound files a little more clear. </p>
<h3>Increasing the playback frequency to 16kHz</h3>
<p>You can also downsample your wav to a less extreme 16kHz audio file. This allows higher frequencies to be represented better (something something Nyquist), and also allows voice files to sound better. I will warn you however that it takes twice as much memory, meaning you can hold maybe 2 seconds of audio now instead of 5? It's significant compared to what it originally was, which was not much.</p>
<p>If you use a 16kHz .wav without changing the code, you'll hear a slowed down version of the file. This is because it still thinks you're using an 8kHz file. To hear a 16kHz encoding, you'll need to download the mellis PCM file from the high-low tech blog post. This allows you a lower level access to the PCM code, which lets you change some variables around to playback faster. It sounds a little better too!</p>
<h3>Playback from SD card</h3>
<p>I haven't tried this, but I hope to soon!</p>
<p>That's all for now, have fun!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/arduinodirect2wav/</guid>
      <pubDate>Sun, 13 Dec 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Analog Electronics Basics: Scaling Voltage Rails</title>
      <link>https://andykong.org/blog/analogscalingvoltages/</link>
      <description>What to do when your circuit outputs -5V to +5V but your ADC only takes 0-5V</description>
      <content:encoded><![CDATA[<html><body><p>The problem I'm going to be talking about today is how to translate an analog signal from one range to another. I have dealt with this problem three times now, each time was more unintuitive than the last so I thought it'd be useful if I wrote down my process for other people to see what's going on and solve their similar problems.</p>
<div style="background:#1a1a2e; border-left:4px solid #E0B890; padding:12px 16px; margin:1.5em 0; border-radius:4px;">
<strong style="color:#E0B890;">Update:</strong> I've since made a calculator that solves this for you automatically — pick your input/output rails and it gives you resistor values. <a href="../../projects/analogscalingcalculator/">Check it out here.</a>
</div>
<p><img alt="Mapping analog voltages between two rails" src="/static/analogscalinggraphic.png"/></p>
<p class="caption">Mapping analog voltages between two rails</p>
<hr/>
<h3>An example of this problem</h3>
<p>Let's say you're using a complicated device, which takes in some analog/digital signal in order to control some aspect of itself. As an example, let's say it controls its position. You go to measure the device's current position to control what position you actually want it to go to, but discover from the datasheet or your fried microcontroller that the analog signal out from the device has a different rail-to-rail voltage than your microcontroller. It runs at ±15V and your board runs from USB's 0-5V! </p>
<p><img alt="Mapping of voltages that would fix your problems" src="/static/voltagescalingmath1.png"/></p>
<p class="caption">Mapping of voltages that would fix your problems</p>
<p>You grab a new Arduino from the scrap heap and think about how to fix this problem. So clearly, if you could just divide all signals by six and then add 2.5V, the rails would match and you would be able to lock onto the signal from the microcontroller. Voila! Easy as cake. Implement an adder op-amp, then have another op amp with feedback resistors so that it divides itself by 6. </p>
<p>However, you can do it using just one op amp. Introducing, the differential op amp!</p>
<p><img alt="Difference op amp, or subtractor op amp schematic" src="/static/diffamp1.png"/></p>
<p class="caption">Difference op amp, or subtractor op amp schematic</p>
<h3>What they don't tell you</h3>
<p>So most of the top Google results that show you how to implement a differential or subtractor op-amp configuration place some limitations on the problem to make it easier to solve. Maybe they say the resistors R1/R3 and R2/R4 have the same ratio, like the first result on Google, the <a href="https://www.electronics-tutorials.ws/opamp/opamp_5.html">electronics-tutorials</a> site.</p>
<p><img alt="electronics-tutorials subtractor op-amp configuration" src="https://www.electronicshub.org/wp-content/uploads/2015/01/1.-Differential-amplifier-circuit.jpg"/></p>
<p class="caption">Electronics Tutorials subtractor op-amp configuration</p>
<p>Or, they might assume that the signal is zero-centered when telling you the gain. Both the site I cited above and the 2nd result on Google <a href="https://www.electronicshub.org/differential-amplifier/">(this site)</a> assume both of these things. But this isn't necessarily what you're looking for. It doesn't matter if their example op amps solve for </p>
<p><img alt="Shitty solution of the differential op-amp" src="/static/analogscaling_shitsoln.png"/></p>
<p>, that doesn't move the center voltage at all. And it gets worse if you want uneven gain on either side of the center, which may not be zero. What to do?</p>
<p><br/></p>
<hr/>
<h3>How to solve this</h3>
<p>Here's the full solution.</p>
<p><img alt="Schematic of the problem statement" src="/static/scalingvoltageschematic.png"/></p>
<p>Given this circuit above, </p>
<p><img alt="Full solution to the differential op-amp" src="/static/analogscaling_fullsoln.png"/></p>
<p>Seems easy, right? It is! And it shouldn't have been so hard for me to figure that out!</p>
<h3>How's it work?</h3>
<p>So let's go over it. R1 and R2 are feedback for the Vin, they do the dividing of Vin in the circuit. The tricky part is the Vref, and especially how it interacts with the Vin. It gets subtracted, sure, but if Vref is not zero then it's going to show up in your final output as well. </p>
<p>You may be saying "Ohh, but Andy, this isn't like a differential op-amp at all! The noninverting input doesn't even have resistors." Well to that I say, how do you expect to make your reference voltage? You'll usually have to make a voltage divider to ground, and voila, the original subtractor structure appears.</p>
<h3>Example please?</h3>
<p>Sure! Let's say we're solving the above mapping: from ±15V rails to a 0-5V range. This op-amp is in an inverting configuration (negative feedback), so the higher initial voltage is going to have to map to the lower voltage on the output, and vice versa. Here's what we're trying to accomplish.</p>
<p><img alt="Mapping of voltages that would fix your problems, pt. 2" src="/static/voltagescalingmath2.png"/></p>
<p class="caption">Mapping of voltages that would fix your problems, pt. 2</p>
<p>We can just plug in our knowns and solve the linear system of equations for Vref and the resistor ratio. Let's say R2/R1 = r just so we can write it more easily. From the first equation, we have Vin = 15V, Vout = 0V, and the second equation we have Vin = -15V and Vout = 5V. So in LaTeX form:</p>
<p><img alt="Equation one" src="/static/analogscale_eq1.png"/></p>
<p><img alt="Equation two" src="/static/analogscale_eq2.png"/></p>
<p>Then we just ask Wolfram Alpha! I'm using v for Vref to make it easier to type.</p>
<p><img alt="Wolfram Query" src="/static/wolframquery.png"/></p>
<p><img alt="Wolfram Answer" src="/static/wolframanswer.png"/></p>
<p>Great! So our R1 has to equal 6*R2, and our reference voltage should be somewhere around 2.14V. </p>
<h3>Implementing the resistor ratio and voltage divider</h3>
<p>I want to pick realistic resistors because these circuits are usually needed immediately and in real-life, so I usually use a resistor ratio calculator to make this easier. <a href="http://jansson.us/resistors.html">This site</a> is a godsend. I'm really lazy in real life, so I'm only going to use the single resistor in series option, but the other ones are usually a little or a lot better in terms of error. </p>
<p>For the resistors with a ratio of 1/6, it seems a good choice is a 56kΩ and 330kΩ resistor. </p>
<p><img alt="Resistor ratio solving for r" src="/static/analogscaling_r.png"/></p>
<p>We'll assume we have access to a 5V source since the second set of rails is 0-5V. We can use the handy voltage divider option on the site to solve for this ratio all at once. Looks like a 33kΩ and 47kΩ resistor will do the trick. </p>
<p><img alt="Resistor ratio solving for Vref" src="/static/analogscaling_vref.png"/></p>
<p><br/></p><hr/>
<h2 id="does-it-work">Does it work?</h2>
<p>Here's the part values we picked out in simulation:</p>
<p><img alt="Vout with +15V is near zero, around -0.1V" src="/static/analogscaling_solutionpos.png"/></p>
<p><img alt="Vout with -15V is almost exactly 5V" src="/static/analogscaling_solutionneg.png"/></p>
<p>The +15V rail becomes -0.13V, so very close to the 0V we wanted it to be. The -15V rail nails 5V almost exactly. So yea, I'd say they work. </p>
<p>Sometimes the solution with realistic resistors will need some tuning, because negative voltages will usually damage a circuit, but you can just tune the initial parameters on the initial voltage mapping to be a little tighter and solve the problem again. </p>
<h2 id="closing">Closing</h2>
<p>Anyway, I encounter this problem all the time, and as I work more with hardware I think this is an integral "glue" circuit that you should master if you're going to work with hardware of various logical and analog levels. That's all for now, cya next time!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/analogscalingvoltages/</guid>
      <pubDate>Tue, 08 Dec 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Remote Photoplethysmography (PPG) Tutorial</title>
      <link>https://andykong.org/blog/PPG_tutorial/</link>
      <description>A guide on how to pronounce the word, and how to implement it in Python</description>
      <content:encoded><![CDATA[<html><body><p>Here's the code if you want to give it a shot yourself: <a href="https://gist.github.com/kongmunist/ba659019a483117a846dc2101e27f13d">gist</a>. You will need to download the Haar cascades yourself though.</p>
<hr/>
<p>Today I'm going to be telling you about this sick computer vision technique that requires only your webcam feed to get your heartrate. And no, I'm not talking about the phone flashlight trick. I'm talking photoplethysmography (PPG)!</p>
<p>Existing cameras on phones and laptops are amazing, their resolution has allowed some really cool sensing techniques besides taking great photos of your Chipotle burrito. Even knowing this, I found this technique hard to believe. </p>
<p>I was reading about a project from Microsoft Research called <a href="https://www.microsoft.com/en-us/research/project/cardiolens/">CardioLens</a>, which projected people's heartrates onto their faces using just the camera in a Hololens headset. When your heart beats, your blood vessels swell out a little from the sudden pump motion of the heart. According to this paper, we can see this cyclical pumping as the blood vessels swell and shrink just by averaging the intensity of the colors on someone's face. With a normal camera!</p>
<p class="caption"> Cardiolens pulse signal </p>
<p><img alt="Cardiolens pulse signal" class="addpic" src="/static/cardiolens_danielmcduff.png"/></p>
<p>Nuts, right? I procrastinated trying this project despite how simple it was reported to be, but after reading about it on <a href="https://www.jimmynewland.com/wp/about-jimmy/presentations/remote-ppg-gui/">Jimmy Newland's website</a>, I felt I could give a decent crack at it. </p>
<p><img alt="Face and eye detection using Haar Cascades" src="/static/ppg.gif"/></p>
<p class="caption">Face and eye detection using Haar Cascades</p>
<p>Setting up a webcam feed from OpenCV is pretty easy, as is using Haar Cascades for face detection. I downsampled the webcam to a 1/16 of the original size to run face detection at 20 FPS, then ran it through SciPy's FFT (technically the power spectral density). Voila! </p>
<p><img alt="PPG from a crop on my forehead" src="/static/PPG_forehead.png"/></p>
<p class="caption">PPG from a crop on my forehead</p>
<p>Jimmy recommended using the forehead patch, but I got much better signal from a crop of my webcam under my eyes (cheeks are known for their blush). Interestingly enough, I was reading <a href="https://www.osapublishing.org/oe/viewmedia.cfm?uri=oe-16-26-21434&amp;seq=0">one of the earlier papers</a> on this and they recommended using the green channel and not the red. Very surprising to me, considering most light used in biosensing depends on red light being more permeable in our skin than the other colors. </p>
<p>The final signal is only 1-2 intensity level changes of the average on my face. It's kinda crazy to me to know that we can get that kind of noise-free resolution after averaging. Amazing what we can do with today's sensing capabilities.</p>
<p><img alt="PPG from a crop under my eyes, with better signal. You can see the fluctuation of my face intensity from the graph itself" src="/static/PPG_forehead.png"/></p>
<p class="caption">PPG from a crop under my eyes, with better signal. You can see the fluctuation of my face intensity from the graph itself</p>
<p>You also have to be holding incredibly still. Any movement changes the lighting on your forehead, which screws up your intensity chart massively. Even the back-and-forth movement caused by your heart beating screws it up, but if you hold super still it works very reliably. </p>
<p>I want to use this for an art project, but the error arising from movement makes it impossible. Not sure how to fix either, since the error depends on your lighting environment. </p>
<p>That's all for now. Cya next time!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/PPG_tutorial/</guid>
      <pubDate>Mon, 07 Dec 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Exploring USB-A breakout cables</title>
      <link>https://andykong.org/blog/USBA_exploration/</link>
      <description>The normal USB cable</description>
      <content:encoded><![CDATA[<html><body><p>Recently I had the need for a 5V battery to power a circuit. </p>
<p>Lithium polymer/ion batteries are powerful and cheap because of the hobbyist drone market, but come in multiples of 3.7V-4.2V. NiMH (AA and AAA) batteries are plentiful and expensive, but only come in intervals of 1.5V (9V is also composed of 6 similar batteries wrapping together. Who knew?!). These would be fine, except I didn't want to deal with a voltage regulator since this was just a quick test. What to do?</p>
<p>The wall outputs 5V through a phone charger, but I (1) needed it to be wearable and (2) incapable of killing you if all the op amps and resistors shorted in just the right way. </p>
<p>I had an extra portable phone charger battery I had gotten from a career fair, which output 5V at a capacity of 2200 mAh. Most importantly, I already had it. </p>
<p><img alt="Career fairs are good for something" src="/static/ThanksIBM.jpg"/></p>
<p class="caption">Career fairs are good for something</p>
<p>I destroyed a USB cable (the other end was this weird flat thing) and plugged it in after charging the battery. Voltmeter confirms, &gt;5V. </p>
<p><img alt="A multimeter measuring my portable phone battery's voltage" src="/static/USBA_realvoltage.jpg"/></p>
<p class="caption">A multimeter measuring my portable phone battery's voltage</p>
<p>I expected these batteries to be more regulated, though I suppose there aren't many electronics parts that work at 5V that don't work also at 5.2V. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/USBA_exploration/</guid>
      <pubDate>Fri, 04 Dec 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Sledding on a table</title>
      <link>https://andykong.org/blog/sleddingontable/</link>
      <description>We had no sled, but we did have an extra folding table</description>
      <content:encoded><![CDATA[<html><body><p>First sticking snow in Pittsburgh today! Me and Elio and Jona wanted to sled, but couldn't find a sled equivalent around the house. Cardboard too soggy and small, container lids too flimsy and weak, no detachable thing on those trashcans anymore :(. What to do? We looked around and found this folding table we had!</p>
<p><img alt="Place your bets as to whether this table makes a good sled" src="/static/tableforsledding.JPG"/></p>
<p class="caption">Place your bets as to whether this table makes a good sled!</p>
<p>It works! Video on my instagram <a href="https://www.instagram.com/p/CIR1Gn6nJ6u/">here</a>. Not super well since it's so heavy, but you can lift the front and sort of glide down a hill over the ice instead of just plowing into it. Pretty good!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/sleddingontable/</guid>
      <pubDate>Tue, 01 Dec 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Myo3 Noise Specs</title>
      <link>https://andykong.org/blog/myo3_noisespecs/</link>
      <description>1.24µV of noise at 500,000x gain on a DIY electromyography sensor</description>
      <content:encoded><![CDATA[<html><body><p>Today I tested the third iteration of my DIY EMG sensor. </p>
<p><img alt="Fresh myo3 boards from the oven" src="/static/myo3_board.png"/></p>
<p class="caption">Fresh myo3 boards from the oven</p>
<h3>Measuring the total gain</h3>
<p>My function generator only goes down to 20 mV p-p, I'm using a 50kΩ and 10Ω resistor to make a voltage divier to divide my input signal by 5000.</p>
<p>I'm using a 200Hz sine wave, since that's the center frequency of all my filtering. Its amplitude is 50 mV p-p. Afte the voltage divider, the signal actually going into the board is a 10 µV p-p sine wave. After the first stage, the output becomes 50 mV on the scope. It's like a magic trick — first we disappear the signal, then we make it reappear again!</p>
<p>I lowered my output amplitude to as low as it could go, 20 mV p-p. This becomes 4 µV p-p after the divider, which becomes 20 mV after the first stage. What happens after the second stage? Glad you asked:</p>
<p><img alt="A choppy 2V sine wave, produced from a 4µV signal" src="/static/myo3_100hz.JPG"/></p>
<p class="caption">A choppy 2V sine wave, amplified from a 4µV input signal</p>
<p>We see that the 20mV signal becomes 2V, a gain of 100. After both stages, the total gain of a myo3 board is 500,000x, or 114 dB of gain. But that's not impressive if the noise is high — so what <em>is</em> the noise?</p>
<h3>Noise measuring setup</h3>
<p>I'm connecting the two inputs to the INA, and then grounding them to the center voltage of the power rails. </p>
<p><img alt="Noise testing setup for myo3" src="/static/myo3_noiseboardsetup.JPG"/></p>
<p class="caption">Noise testing setup for myo3</p>
<p>And here's what the output looks like after both stages:</p>
<p><img alt="Output signal of the myo3 after shorting its inputs. Peak to peak noise is 620 mV" src="/static/myo3_out3noise.JPG"/></p>
<p class="caption">Output signal of the myo3 after shorting its inputs. Peak to peak noise is 620 mV</p>
<p>Looks bad right? Well, let's do the calculation. </p>
<p>After 500,000x gain, the output noise has a peak-to-peak voltage of 600-900mV. 0.62V/500000 = 0.00000124, or <em>1.24 µV</em>. </p>
<p>The total noise on the inputs is under 2 µV, before any digital filtering or averaging. Wow! And that's across 50kΩ of resistance, meaning its got low current noise AND voltage noise. For reference, that's less than the thermal noise on a 100kΩ resistor at 20C and 1000Hz bandwidth. </p>
<p>I think this circuit definitely achieves my original goal of making a biosensing board that doesn't have awful noise dwarfing the measurements. We'll see how it fares in a real test when my USB isolator arrives.</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/myo3_noisespecs/</guid>
      <pubDate>Sun, 29 Nov 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Why are Javascript libraries so annoying</title>
      <link>https://andykong.org/blog/jslibraries/</link>
      <description>What do you mean everything is a global???</description>
      <content:encoded><![CDATA[<html><body><p>I am writing a rather complex Javascript project at the moment, and needed to include two different Javascript libraries. One is <a href="https://p5js.org/">p5.js</a>, which is just Processing ported to HTML and used for drawing and a lot of visual display stuff. The other is <a href="https://mlweb.loria.fr/">mlweb</a>, which is a machine learning library written in pure Javascript. </p>
<p><u><strong>One of the cursed things about using Javascript is that most libraries are written exclusively as a global import</strong></u>, so importing two large libraries is a nightmare if any of their functions overlap. While the ES6 standard (2015 major update to Javascript) recommends libraries encapsulate their functions into their own namespace, older ones or lazy ones still don't do it.</p>
<p>This is annoying in Javascript, but isn't a problem at all in Python. Importing a library by default forces you to use their import name before any function or variable from that library (think <code>cv2.imread()</code> from <code>import cv2</code>, or <code>os.walk</code> from <code>import os</code>). </p>
<p>In my case, <code>sin</code> and <code>tan</code> and a few other math functions overlapped. Though both functions do the same thing, the problem arises because of the format that they each return. If the p5.js function returns their own P5 class of int or Array, then the mlweb function calling it wouldn't be able to parse its output and crash. </p>
<h3>Maybe there's already a fix for it?</h3>
<p>I couldn't find anything after an hour of looking, so I made a <a href="https://stackoverflow.com/questions/64730996/how-can-i-include-a-javascript-library-with-a-namespace-without-manually-exporti">post about it on SO</a>. It was my first post, and was answered in 4 minutes in the negatory. Turns out there's no way to import Javascript like this: </p>
<p><code>&lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"&gt;&lt;/script&gt;</code></p>
<p>And have it callable by module namespace. Wack!</p>
<h3>Is there anything we can do?</h3>
<p>For small libraries, it's doable. You can wrap your entire library.js file in a module declaration and then export each function you want to be able to use <a href="https://www.typescriptlang.org/docs/handbook/namespaces-and-modules.html">(source)</a>:</p>
<p><code>declare module "SomeModule" {</code><br/>
 <code>export function fn(): string;</code><br/>
<code>}</code></p>
<p>But this doesn't work for large libraries, since there are so many variables and functions and all kinds of stuff, and as far as I know there's no automatic tool for it. However, I think you could write something simple that would just parse a javascript file and export everything for you. </p>
<p>Now that I think about it, I did something like this for my <a href="https://andykong.org/projects/heartratemonitor/">Webcam Heartrate</a> project. I ported a C++ package to Node.js using Emscripten, then used Browserify to port that to a script that I could just include. Then to call it, I added some lines around the library to expose it as a variable, and had to reference that variable to call its functions. May be useful for me to go back through and see if I can't throw together a quick solution...</p>
<h3>Final fix</h3>
<p>In the end, it turns out p5.js DOES conform to ES6, and can be wrapped in a big function wherever you call its functions and variables to not leak into the global namespace. With this solution, I stopped looking further into it. You can also invoke p5.js whenever you want in your javascript, if the problem is just the order in which the functions overlap (lazier, but easier).</p>
<p>A Javascript encapsulator script would be really useful if Javascript continues being the norm for web stuff and people push more and more code into Javascript. It doesn't seem super hard, but for now there's not much of a fix and I'm busy. Summer project, anyone?</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/jslibraries/</guid>
      <pubDate>Sat, 28 Nov 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Teensy DAQ: ADC Oversampling on the Teensy 4.0 microcontroller</title>
      <link>https://andykong.org/blog/teensydaq/</link>
      <description>300 kSPS to 1 kSPS faster than you can say "free bits"</description>
      <content:encoded><![CDATA[<html><body><p>I recently wrote a <a href="https://github.com/kongmunist/TeensyDAQ-Fast">Teensy Data Acquisition GUI</a> for recording data from a microcontroller/sensor onto the computer. It's pretty simple, just a Python QT5 GUI that has a plot of the data as it comes in over the serial and writes it into a list as fast as it can. After you hit 'stop recording', it writes it into a text file. And yes, this order was the fastest. I checked.</p>
<p>I felt wasteful when first using it because I only wanted to get signals at around 1kHz at most, while the Teensy analog-digital converter running at full-tilt with some averaging (noise reduction) still sampled at well over 200 kHz. What to do with these extra samples? Get extra bits, of course!</p>
<p><img alt="Free-running ADC goes at 362kHz" src="/static/teensydaq_ADCoutput.jpeg"/></p>
<p class="caption">Free-running Teensy ADC goes at 362kHz — and it's got two of 'em!</p>
<p>So while you can average 2<sup>n</sup> samples to reduce the Noise Power by a factor of n, you can't quite do the same with bits of resolution from your ADC. Turns out when you sum two measurements, their noise magnitude grows by a factor of sqrt(n), while your signal magnitude only grows by a factor of n. The SNR only improves by sqrt(n), since n/sqrt(n)... well, you understand. More on that <a href="https://en.wikipedia.org/wiki/Oversampling#Resolution">here</a>. Basically, you need to sample 4 times per bit of extra resolution you want to get. So if you wanted one more bit, you'd oversample at 4x the Nyquist frequency, if you wanted two you'd have to sample at 4<sup>2</sup>, etc.</p>
<p>However, I'm still not sure exactly how SNR relates to bits of resolution. I understand the math, but not intuitively. It still feels like you could average two binary numbers to get one that has one more bit of info. Maybe it only has 0.5 more bits of extra information? That would definitely correspond to it having "less noise," which it does after averaging... I definitely don't understand completely. </p>
<p><br/></p>
<h3>Noise is necessary</h3>
<p>Also, I learned that you NEED noise on your ADC input. Luckily microcontroller voltage references are not the most stable thing in the world, so you usually have enough noise on the pin to wiggle the signal up and down already. I read on some Stackoverflow post that if God made you a 10-bit ADC that was perfectly stable, it could not be directly oversampled to get extra bits of resolution. It'd always give the same answer. Some systems couple a sawtooth wave through a capacitor onto the sampling pin as a form of dithering noise, but I think I don't need to do that. </p>
<h3>So how many bits can we really get?</h3>
<p>So the Teensy ADC goes at 362 kHz, and gives 12 bits natively. We can get 16 bits at 362000/256 = 1414 Hz, and maybe one more bit for a 350 Hz sampling freq. Not bad! We've upgraded our ADC for no noticeable loss, besides maybe some noise loss we could've used the averaging for. I think if we use both ADCs to sample, we'd reduce the noise and perhaps even get 17 bits at 700 Hz, but that's a really minor improvement. </p>
<p>Anyway. I'll try it later, but this is what I've been exploring for now. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/teensydaq/</guid>
      <pubDate>Fri, 27 Nov 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Myo3 EMG sensor initial tests</title>
      <link>https://andykong.org/blog/myo3_initialtesting/</link>
      <description>It's like they always say, the hard part's the software</description>
      <content:encoded><![CDATA[<html><body><p>I finally got around to powering on those EMG circuits I made. </p>
<h2 id="initial-test">Initial test</h2>
<p>For an initial test of any circuit, I was taught to flick the power on and then immediately off. If the current indicator spikes, you know there's a path error, but it might not have burnt out the chip just yet. The idea being not enough power has passed through and messed up the delicate silicon in the chip. I did just this flick trick, and saw a massive current jump immediately :(.</p>
<p>I wracked my brains and traces for any mistakes I could've made in the wiring, but failed to find anything. Went back to the schematic, because maybe I had put in a path to ground somehow? Maybe switched out a bypass capacitor for a bypass resistor? I saw nothing of the sort.</p>
<p>A day later, I looked again at the behest of a friend helping me debug, and found this:</p>
<p><img alt="Flipping the positive and negative supplies is a big no-no" src="/static/myo3_schematicflippedpower.png"/></p>
<p class="caption">Flipping the positive and negative supplies on anything is a big no-no</p>
<h2 id="fixing-the-problems">Fixing the problems</h2>
<p>Luckily, I had only messed up on one chip, a quad op-amp. I was able to just rotate it 180° and have all the same inputs and outputs. Unluckily, it was on the TSSOP|16 package. Excuse the flux everywhere, but here's the chip after fixing.</p>
<p>I also had no solder-wicking braid, so had to fashion some out of a flux pen and some stripped, stranded wire. Worked surprisingly well, but went through wire rather quickly compared to the braided stuff. Wire's cheap anyway. </p>
<p><img alt="Fixed the power on this quad amplifier" src="/static/myo3_fixingpower.jpg"/></p>
<h2 id="actual-initial-test">Actual initial test</h2>
<p>My function generator set to 100 Hz sine wave has a minimum output voltage of 20 mV peak-peak. I'm trying to measure a signal approximately 5 uV p-p in size, so I made a voltage divider from a 50kΩ and 10Ω resistor. 20 mV becomes around 4 uV, and I have a high resistance measuring path to simulate the skin impedance. </p>
<h3>First stage, G = 1000</h3>
<p>The sine wave first visibly appears on the output of the INA (first stage) at around 20 uV p-p, standing at around 50 mV p-p for a total gain of ~1000. This is pretty good, but the next stage is saturated at 12 Hz. I believe I accidentally made an oscillator by being greedy and adding gain to a perfectly good 2nd-order Sallen-Key high-pass filter, but I can rectify this by messing with some resistors. 
~</p>
<h3>Shorted inputs noise</h3>
<p>With inputs shorted with a wire, there's a little bit of 60Hz line pickup. That aside, the noise p-p appears to be 70 mV after a 1000x gain, putting the initial, full spectrum noise at 70 uV. As it stands, much of the signal is very high frequency noise. After the RFI/EMI filtering it should be much better since the bandwidth gets heavily reduced, down to around 300Hz. </p>
<p><img alt="Shorted inputs signal on INA output" src="/static/myo3_shortednoise.jpg"/></p>
<p>I'll post further updates after I fix the filters. No board ever works its first time, but it gets there if you want it to!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/myo3_initialtesting/</guid>
      <pubDate>Thu, 26 Nov 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Speed Check — Generating a list of a range of a len</title>
      <link>https://andykong.org/blog/speedcheck_listlen/</link>
      <description>Why does matplotlib set_data not take an iterable :(</description>
      <content:encoded><![CDATA[<html><body><p>As part of my <a href="https://twitter.com/redlightguru/status/1331513267697676289?s=20">PPG project</a>, I had to create a live-updating plot. I'm most familiar with the matplotlib library, which has the same color scheme as MATLAB, so I'm using that. One problem is that they try to make a general-purpose plotting tool (matplotlib.pyplot) that also leaves you free to not worry about which window holds which figure and which renderer is drawing what, when. This is really bad because more general tools are more complex to use, and this is definitely the case here.</p>
<p>It's usually imported as <code>import matplotlib.pyplot as plt</code>. To create a 2-part plot involves creating Axes, which are done by <code>plt.subplot(rows, cols)</code>. Then you write <code>line = ax.plot(x,y)</code>, which doesn't seem bad until you try to <em>use</em> the <code>line</code> object you just created, and then you find out you have to call <code>line, _ = ax.plot(x,y)</code> since it gives multiple things back. </p>
<p>I won't do a full rant since that's not the point, but basically it's annoying, and if you call <code>ax.plot()</code> again, it creates a new line that overlaps with the old line. It's a pain. I needed two live-updating plots, so you can save the <code>line</code> object as a variable and call <code>line.set_data(x,y)</code> to change the data without adding a new line onto the graph. </p>
<p>Now, my data was time-series, and I just wanted the index of the array as the x axis. So for data like <code>[0.5, 1.3, 2.4]</code>, I wanted an x list that was <code>[1, 2, 3]</code>. This is easy enough, it's just <code>list(range(len(data)))</code>. And no, I tried using just <code>range(len(data))</code>. It has to be complete. I also thought of using enumerate, and taking only the first element in a list-comprehension. So I decided to compare them.</p>
<p>This was my first time using the <code>timeit</code> library, and as far as I could tell, it was kinda spotty in terms of consistency. Here's the code:</p>
<!-- HTML generated using hilite.me -->
<div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><table><tr><td><pre style="margin: 0; line-height: 125%"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28</pre></td><td><pre style="margin: 0; line-height: 125%"><span style="color: #008800; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">timeit</span>

uniqueTimes <span style="color: #333333">=</span> <span style="color: #0000DD; font-weight: bold">10</span>
b <span style="color: #333333">=</span> [<span style="color: #0000DD; font-weight: bold">0</span>]<span style="color: #333333">*</span><span style="color: #0000DD; font-weight: bold">10000</span>

<span style="color: #008800; font-weight: bold">def</span> <span style="color: #0066BB; font-weight: bold">lenList</span>(lst):
    <span style="color: #008800; font-weight: bold">return</span> [x1 <span style="color: #008800; font-weight: bold">for</span> x1,x2 <span style="color: #000000; font-weight: bold">in</span> <span style="color: #007020">enumerate</span>(lst)]
<span style="color: #888888"># 0.7 ns/elem</span>

<span style="color: #008800; font-weight: bold">def</span> <span style="color: #0066BB; font-weight: bold">lenList2</span>(lst):
    <span style="color: #008800; font-weight: bold">return</span> <span style="color: #007020">list</span>(<span style="color: #007020">range</span>(<span style="color: #007020">len</span>(lst)))
<span style="color: #888888"># 0.22 ns/elem</span>

<span style="color: #008800; font-weight: bold">def</span> <span style="color: #0066BB; font-weight: bold">mapList3</span>(elem, count <span style="color: #333333">=</span> <span style="color: #0000DD; font-weight: bold">0</span>):
    count <span style="color: #333333">+=</span> <span style="color: #0000DD; font-weight: bold">1</span>
    <span style="color: #008800; font-weight: bold">return</span> count<span style="color: #333333">-</span><span style="color: #0000DD; font-weight: bold">1</span>
<span style="color: #888888"># 3.7 ns/elem</span>

functs <span style="color: #333333">=</span> [<span style="color: #008800; font-weight: bold">lambda</span> : lenList(b),
            <span style="color: #008800; font-weight: bold">lambda</span> : lenList2(b),
            <span style="color: #008800; font-weight: bold">lambda</span> : <span style="color: #007020">list</span>(<span style="color: #007020">map</span>(mapList3, b))]

timesEach <span style="color: #333333">=</span> <span style="color: #0000DD; font-weight: bold">1000</span>
<span style="color: #008800; font-weight: bold">for</span> i <span style="color: #000000; font-weight: bold">in</span> <span style="color: #007020">range</span>(<span style="color: #007020">len</span>(functs)):
    times <span style="color: #333333">=</span> []
    <span style="color: #008800; font-weight: bold">for</span> j <span style="color: #000000; font-weight: bold">in</span> <span style="color: #007020">range</span>(uniqueTimes):
        times<span style="color: #333333">.</span>append(timeit<span style="color: #333333">.</span>timeit(functs[i], number<span style="color: #333333">=</span>timesEach))
    <span style="color: #008800; font-weight: bold">print</span>(i, <span style="background-color: #fff0f0">"ns/elem:"</span>, <span style="color: #007020">sum</span>(times)<span style="color: #333333">/</span>timesEach<span style="color: #333333">*</span><span style="color: #6600EE; font-weight: bold">1e6</span><span style="color: #333333">/</span><span style="color: #007020">len</span>(b))
</pre></td></tr></table></div>
<p>Turns out the naive <code>list(range(len(x)))</code> solution was the fastest! Until next time, cya!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/speedcheck_listlen/</guid>
      <pubDate>Wed, 25 Nov 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Putting together Myo3, the 3rd iteration of my EMG sensor</title>
      <link>https://andykong.org/blog/myo3_assembly/</link>
      <description>My third PCB!</description>
      <content:encoded><![CDATA[<html><body><p>The PCBs came sometime last week, and I got the rest of my resistors and passives today. Time to construct!</p>
<p>My housemates still are quarantining, so I was trying to stay out of the hallways and common areas (like the workshop we have -.-). I moved all parts into my room, but my desk definitely wasn't bright enough. We make do though. Big shoutout to <a href="https://github.com/openscopeproject/InteractiveHtmlBom">HTMLBom</a> for enabling my board construction, it's a super useful plugin for KiCAD and you should get it immediately.</p>
<h3>Cool parts of the board:</h3>
<ul>
<li>
<p>Using the TLV2624 to create a virtual ground from my single-sided power supply. It's the laziest way to do it, but I never claimed to be able to do power well.</p>
</li>
<li>
<p>This board uses the input stage op-amp with optimal noise, assuming dry electrodes (1 MΩ resistance). This is the LME49721, with 4nV/rt(Hz) voltage noise and something like 1 fA/rt(Hz) current noise. Funnily enough, it's an audio amp, with lots of Thad graphs all over its datasheet. If it works it works!</p>
</li>
<li>
<p>I made two boards actually. After the initial LME49721, I switch to an op amp that can source a little more current and drive more capacitance (for the analog filtering). </p>
<ul>
<li>One board uses the OPA4202, which can drive INFINITE capacitive load. I chose it because it can do that.</li>
<li>The other uses the POPA4991, which can only drive 1nF cap load at gain=1. I chose it because it only needs supply voltage similar to the LME49721, meaning I can use a LiPO to power the board instead of trying to source 5V somewhere.</li>
</ul>
</li>
<li>
<p>It has pads on the back! This allows you to strap it directly to your arm to measure muscle signals. I got really sick of putting those 3M red dot electrodes all over myself, especially when they left glue residue all over me. </p>
</li>
<li>
<p>Guard ring 😎 (really it's kind of useless cause I didn't put it on the back as well, but it's the thought that counts (also, I don't really care about input bias current, up until 1 pA or so. Then it generates (G=100000)*1pA*(1 MΩ skin impedance) = 1 V offset at the end of the circuit, which would definitely pin it to the rails!))</p>
</li>
</ul>
<p><br/></p>
<h3>Problems I ran into:</h3>
<ul>
<li>
<p>TSSOP 16 pin is <em>super</em> tight, it's hard to spudge properly (took me 3 tries). </p>
</li>
<li>
<p>I only had 0402 and 0803 parts, but guess what footprint size I decided to use?? :)))))) Luckily both work, the 0402 is really stretching to reach both pads and it doesn't tack as well to the solder paste, but it still works. </p>
</li>
<li>
<p>Also I shouldn't have picked a black solder mask, I can hardly see the 0402 resistors</p>
</li>
<li>
<p>I didn't have 220nF capacitors at all, so I sorta just... skipped it. Luckily, if you know the board, you can just decide whether or not you need a part. This particular one was part of a passive high pass filter, which came before a 2nd order active high pass at the same cutoff frequency. I just removed the cap and resistor, and bridged the resistor pads afterwards. </p>
</li>
</ul>
<p><br/></p>
<h3>Here's some pics!</h3>
<h4>Front after spudging</h4>
<p><img alt="Front of my EMG v3 board" src="/static/myo3front.jpg"/></p>
<h4>Back of board, with integrated pads</h4>
<p><img alt="Back of my EMG v3 board" src="/static/myo3back.png"/></p>
<p>Gonna spec its noise and stuff soon!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/myo3_assembly/</guid>
      <pubDate>Sun, 22 Nov 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Histogram Equalization</title>
      <link>https://andykong.org/blog/histogram/</link>
      <description>Using all your dynamic range to make black and white images look sick!</description>
      <content:encoded><![CDATA[<html><body><p>I recently found out about histogram equalization. Let me tell you about it! So first, </p>
<h3>What's a histogram?</h3>
<p>A histogram is just a graph of the intensity values within an image. To construct it, let's assume you have an 8-bit image whose intensities span 0-255. You create a 256-length array full of zeros. Then, you go through your image pixel by pixel. We use its value as an index, and increment the number in that array position. In the end, you'll have an array of the counts of how many times we've seen each pixel value. Here's an example. </p>
<p><img alt="Bread Music" src="/static/breadsong.jpg"/>
<img alt="Bread Music Histogram" src="/static/breadsong_histogram.png"/></p>
<p>It allows you to visualize the relative range/span of the pixels in your image, whether the image is mostly darks or mostly lights. You can (hopefully) already see that with your eyes, but this is a more technical way of seeing it. </p>
<h3>So what's histogram equalization?</h3>
<p>Let's look at an overexposed image's histogram. </p>
<p><img alt="Bread Music" src="/static/breadsong_overexposed.jpg"/>
<img alt="Bread Music" src="/static/breadsong_overexposed_histogram.png"/></p>
<p>You'll see that the histogram is now bunched to the right, brighter side. More importantly, instead of spanning the entire image, the darkest single pixel is pretty far from the left edge. Our pixels now span 100-255. This is bad because we aren't using the entire dynamic range of the image format, but it's also bad because the image just <em>looks</em> bad! </p>
<p>This is where histogram equalization comes in. We can fix this image by re-stretching out the histogram using <a href="https://en.wikipedia.org/wiki/Histogram_equalization#Implementation">this formula</a>. Now the image's pixel values will once again span 0-255 (like the image above). </p>
<p>The results look amazing for large nature photos like this one <a href="https://bl.ocks.org/biovisualize/c31c5eb3bf1c5a72bde9">(source)</a>:</p>
<p><img alt="Nature photography benefits greatly" src="/static/histeqbeforeafter.png"/></p>
<h3>But you said it's only for black and white images! That image earlier is clearly colored!</h3>
<p>OK, you got me. For ordinary, nature images, you can perform the histogram equalization process for each color channel (RGB) separately, then put them back together into one image. However, for smaller images (100x100 pixels-ish), you may not get good results because the histogram distribution for the 3 colors won't line up well. Here's an example where I histogram equalized my eye, and it looks like I got a black eye. </p>
<p><img src="/static/histogram_eye.png" style="display:block"/>
<img src="/static/histogram_blackeye.png" style="display:block"/></p>
<p class="caption">You should see the other guy</p>
<p>That's all for now. Cya!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/histogram/</guid>
      <pubDate>Fri, 20 Nov 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Creating panorama images manually, pt 2!</title>
      <link>https://andykong.org/blog/panoramas2/</link>
      <description>Better stitching by doing something really easy I just forgot to consider</description>
      <content:encoded><![CDATA[<html><body><p>Late last night I was greeted in my dreams by a green, ghastly spectre, who said unto me: "Instead of adding 1 image at a time, did you try stitching 2 and 2, then the two composites? This would greatly reduce the distortion suffered by the latter images," in a spooky voice. I said "No I didn't think of that," then woke up and tried it. It immediately looked way better. Here's the results</p>
<p><img alt="Improved stitching" src="/static/panorama2_betterstitch.jpg"/></p>
<p>I think due to the distortion being lessened, the total features being found are much better for the two pairs than for adding one image at a time. Here's an example of the keypoints being connected across. </p>
<p><img alt="Keypoints comparison across the new method" src="/static/panorama2_keypointcrossanalysis.jpg"/></p>
<p>I guess divide and conquer really has its merits. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/panoramas2/</guid>
      <pubDate>Wed, 18 Nov 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Creating panorama images manually</title>
      <link>https://andykong.org/blog/panoramas1/</link>
      <description>Experiments in holography</description>
      <content:encoded><![CDATA[<html><body><p>Howdy y'all! I think everyone knows what a panorama is so I'll jump right into it. </p>
<h2 id="whats-going-on">What's going on?</h2>
<p>I'm working on a problem where I need to stitch together 4 images in a 2x2 pattern, into a big panorama/composite image. The image sets look something like this: </p>
<p><img alt="My messy, feature-full desk" src="/static/panoramassplit.png"/></p>
<h2 id="how-do-you-do-it">How do you do it?</h2>
<p>I started from a simpler problem of stitching just two images together. At first I thought I could do some naive image stitching — i.e. find the row for both images that have the least difference, and just splice at that spot. However, I realized that is probably too naive, and decided to do some Googling.</p>
<p>So instead, it turns out, you can find "corners"/keypoints/features within an image that are distinctive in angle/size/shape. Given two images with a bit of overlap, you can just find the features for either one. The feature detectors can also generate some 128 or 32-vector embedding of each keypoint, which is then used to compare each keypoint to every other keypoint using some "distance" metric, usually euclidean distance. </p>
<p>Afterwards, you'll have some set of key points that are shared between both images, and the XY locations within both images. These XY points can then be correlated to find some affine transform that... OK, too complicated. Basically, imagine your two images each have a triangle in them. You can find the corners in both, figure out which corner in image1 maps to which corner in image2, and skew your view of one of the images so that the triangles will line up. Then all you gotta do is add the images! Here's an example:</p>
<p><img alt="View 1 of our shapes" src="/static/panorama1_view1.png"/></p>
<p><img alt="View 2 of our shapes" src="/static/panorama1_view2.png"/></p>
<p>Intuitively, you know that in the 2nd image, you're looking at the 1st image from a different viewpoint. Namely, from the bottom up. You also know that, as an approximation, transforming the bottom square into the top one will sorta transform everything else as well to match the top. </p>
<h2 id="implementation-details">Implementation details</h2>
<ol>
<li>Find keypoints/descriptors for each image. I used SIFT, then switched to ORB for speed</li>
<li>Find matching keypoints between the two images. FLANN (Fast Library for Approximate Nearest Neighbors) works well, and can find the two nearest neighbors. This is useful because it lets us filter out bad matches using a <a href="https://docs.opencv.org/3.4/d5/d6f/tutorial_feature_flann_matcher.html">ratio test</a>.</li>
<li>Calculate the homography matrix between the two images using their matching keypoints (homography is the process of mapping one set of vertices to another, like triangle corners to triangle corners). OpenCV has a function <code>cv2.findHomography()</code> for this using RANSAC. It isn't deterministic though :(.</li>
<li>Apply the homography matrix to one of the images using <code>cv2.warpPerspective</code></li>
<li>Combine the images together.</li>
</ol>
<p>Voila!</p>
<p><img alt="Combined pic" src="/static/panorama1_combined.png"/></p>
<h2 id="problems">Problems</h2>
<h5>Seams</h5>
<p>There's weird border/seam issues where the images come together, which I anticipated. I didn't control the ISO precisely on my phone, so there's bound to be issues there. Seam finding is for later, but I think it's possible to apply a uniform shift for every added image to make their borders line up.</p>
<h5>Distortion</h5>
<p>You can clearly see which image was first (top left), and which ones were warped and then added on. I think this is a problem because features become harder to detect after a few images get added, and the last one barely makes it on there in a coherent manner at all. I may be able to do some like detect all shared features before, then transform each set one at a time in order to get better results. Otherwise the alignment is all messed up. Problem for later though!</p>
<h2 id="bonus">Bonus</h2>
<p>In the course of messing with <code>cv2.warpPerspective</code>, I also made some cool glitch art. Enjoy! Until next time ~</p>
<p><img alt="Glitch art" src="/static/panorama1_glitchart.jpg"/></p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/panoramas1/</guid>
      <pubDate>Tue, 17 Nov 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Playstation Eye does not work for MacOS!</title>
      <link>https://andykong.org/blog/eyetoypscam/</link>
      <description>Mac doesn't play nice with weird USB webcams :(</description>
      <content:encoded><![CDATA[<html><body><p>I wasted like an hour and a half today trying to get this Playstation 3 camera working on my Mac:</p>
<p><img class="d-block mx-auto" src="/static/playstationeye.jpg" width="300"/></p>
<p>Let me tell you how I debugged it.</p>
<p>After plugging in, the USB name identifies it as a USB Webcam, but simply refuses to recognize its data like one. Apparently this is a problem with many USB webcams and Mac, necessitating the invention of the <a href="http://webcam-osx.sourceforge.net/">macam project</a>, which exists specifically for installing drivers for USB webcams. It was only maintained from 2006-2007, explaining why it doesn't have a github repo (git was invented 2005), but good thing those drivers don't change! </p>
<p>Except I'm running Catalina, and they wrote their code in 32-bit :(. Someone has been kind enough to port it to GitHub as <a href="https://github.com/smokris/macam64">macam64</a>. </p>
<p>I tried to follow their steps but my CMake version was out of date. I then tried to use homebrew to update my CMake. It took 20 minutes, updating and installing all sorts of other things, and even had the audacity to tell me it had agreed to qt's user agreement on my behalf (threatening that if I disagreed with its decision, I should uninstall QT immediately). When I ran CMake again, I found that the original CMake location was not the one Homebrew had updated. </p>
<p>I stopped here. I hate dealing with pathing issues. If I have it, just go find it!!! Maybe one day I will muster the vernacular to venture forth, but today I sighed, and gave up on this webcam. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/eyetoypscam/</guid>
      <pubDate>Sun, 15 Nov 2020 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Spudging PCB Stencils for Beginners</title>
      <link>https://andykong.org/blog/spudging/</link>
      <description>How to lay out 50 components on a board really fast</description>
      <content:encoded><![CDATA[<html><body><p>Hello! This is Andy. I've made some progress on my EMG circuit since last year, and by that I mean I've iterated exactly once. While assembling my second iteration, I decided to bite the bullet and order a PCB stencil so I would have an easier time putting together the board. It was really quite fun, so I've decided to share the process and my thoughts on it. </p>
<p>You need to have 
- your blank, unpopulated PCB</p>
<ul>
<li>
<p>the PCB's stencil</p>
</li>
<li>
<p>Solder paste</p>
</li>
<li>
<p>a credit card or something similar</p>
</li>
<li>
<p>other blank, unpopulated PCBs to support the stencil</p>
</li>
<li>
<p>tape</p>
</li>
<li>
<p>reflow oven or hot-air gun</p>
</li>
</ul>
<h1 id="steps">Steps</h1>
<h3>1. Tape your PCB down with some of its friends adjacent</h3>
<p><img class="d-block mx-auto" src="/static/spudge1.JPG" width="400"/><br/> </p>
<h3>2. Place your solder mask over the PCB and align it until you can't see any of the green solder mask. Then tape it down.</h3>
<p><img class="d-block mx-auto" src="/static/spudge2.JPG" width="400"/><br/> </p>
<h3>3. Squeeze out a bit of solder paste and get your scraper tool</h3>
<p><img class="d-block mx-auto" src="/static/spudge3.JPG" width="400"/><br/> </p>
<h3>4. Squeegee the paste over the board. Push down, hard, to ensure no leakage. Only do this step 1-3 times, any more and you'll leak it out</h3>
<p><img class="d-block mx-auto" src="/static/spudge4.JPG" width="400"/><br/> </p>
<h3>5. Check the coverage. If there are some pads that have no grey, touch up those areas locally.</h3>
<p><img class="d-block mx-auto" src="/static/spudge5.JPG" width="400"/><br/> </p>
<h3>6. Remove the solder mask tape carefully, then pull it up from one side. Check the footprints, then add your components</h3>
<p><img class="d-block mx-auto" src="/static/spudge6.JPG" width="400"/><br/> </p>
<h3>7. If they're ready to go, pop it in the reflow oven and hit start. Enjoy the fumes for a few minutes, then inspect your board!</h3>
<p><img class="d-block mx-auto" src="/static/spudge7.JPG" width="400"/><br/> </p>
<p>The hardest part is the actual spudging — make sure to apply a lot of pressure, but resist the urge to get solid grey filled in on the pads. Don't go over it more than a few times. You'll have a solid uniform grey coating on all the footprints, but they'll leak out the sides and ruin the pads. That's all for now, be safe!</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/spudging/</guid>
      <pubDate>Mon, 14 Sep 2020 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Privacy screen</title>
      <link>https://andykong.org/blog/privacyscreen/</link>
      <description>Only cool kids can see this laptop!</description>
      <content:encoded><![CDATA[<html><body><p>Howdy! I saw a meme when I was in high school that was a picture of a man. Superimposed onto his bottom text was the phrase "This meme is a red square when you're not looking," the joke being that you wouldn't be able to see it if you weren't looking. Anyway, since then I've had this desire to really implement it, to make some kind of billboard/house sign that nonresidents wouldn't be able to see, perhaps to display the weekly dinner schedule or WiFi password.</p>
<p>Using a Raspberry Pi, raspicam, and downloading dlib's face recognition models, we were able to get it to run at <b>0.5 FPS</b>. When someone it recognized from the "VIP Faces" folder was in view of the camera, it would turn the screen back on. Otherwise, the Raspberry Pi would black out the screen using some display power management functions invoked from the command. </p>
<p>This FPS is a little useless since it can't black out the screen fast enough, so I ported it over to my Mac. Here, it runs at \~10 FPS, so it can feasibly block out someone trying to view your screen. Another benefit is it blacks out the screen while you aren't looking at it (since it can't see your face), saving power (Assuming the display turn off and turn on don't cost any extra energy, which they probably do). Anyway, it only took a few hours to write, so here's all the code: </p>
<!-- HTML generated using hilite.me -->
<div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><table><tr><td><pre style="margin: 0; line-height: 125%"> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62</pre></td><td><pre style="margin: 0; line-height: 125%"><span style="color: #008800; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">face_recognition</span>
<span style="color: #008800; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">cv2</span>
<span style="color: #008800; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">subprocess</span> <span style="color: #008800; font-weight: bold">as</span> <span style="color: #0e84b5; font-weight: bold">sp</span>
<span style="color: #008800; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">os</span>
<span style="color: #008800; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">glob</span>
<span style="color: #008800; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">numpy</span> <span style="color: #008800; font-weight: bold">as</span> <span style="color: #0e84b5; font-weight: bold">np</span>
<span style="color: #008800; font-weight: bold">from</span> <span style="color: #0e84b5; font-weight: bold">functools</span> <span style="color: #008800; font-weight: bold">import</span> <span style="color: #007020">reduce</span>

<span style="color: #888888"># Start webcam stream</span>
video_capture <span style="color: #333333">=</span> cv2<span style="color: #333333">.</span>VideoCapture(<span style="color: #0000DD; font-weight: bold">0</span>)

<span style="color: #888888"># Load approved users</span>
known_face_encodings <span style="color: #333333">=</span> <span style="color: #007020">dict</span>()

rootdir <span style="color: #333333">=</span> os<span style="color: #333333">.</span>path<span style="color: #333333">.</span>dirname(__file__) <span style="color: #333333">+</span> <span style="background-color: #fff0f0">"/faces/"</span>
<span style="color: #008800; font-weight: bold">for</span> fl <span style="color: #000000; font-weight: bold">in</span> os<span style="color: #333333">.</span>listdir(rootdir) :
    <span style="color: #008800; font-weight: bold">if</span> fl<span style="color: #333333">.</span>endswith(<span style="background-color: #fff0f0">".jpg"</span>) <span style="color: #000000; font-weight: bold">or</span> fl<span style="color: #333333">.</span>endswith(<span style="background-color: #fff0f0">".jpeg"</span>) <span style="color: #000000; font-weight: bold">or</span> fl<span style="color: #333333">.</span>endswith(<span style="background-color: #fff0f0">".png"</span>) :
        image <span style="color: #333333">=</span> face_recognition<span style="color: #333333">.</span>load_image_file(rootdir <span style="color: #333333">+</span> fl)
        known_face_encodings[fl] <span style="color: #333333">=</span> face_recognition<span style="color: #333333">.</span>face_encodings(image)[<span style="color: #0000DD; font-weight: bold">0</span>]
known_face_encodings <span style="color: #333333">=</span> <span style="color: #007020">list</span>(known_face_encodings<span style="color: #333333">.</span>values())

<span style="color: #888888"># Initialize some variables</span>
new_face_locations <span style="color: #333333">=</span> []
new_face_encodings <span style="color: #333333">=</span> []
turned_off <span style="color: #333333">=</span> <span style="color: #007020">False</span>
<span style="color: #008800; font-weight: bold">print</span>(<span style="background-color: #fff0f0">'Starting privacy screen'</span>)

<span style="color: #008800; font-weight: bold">while</span> <span style="color: #007020">True</span>:
    <span style="color: #888888"># Grab a single frame of video</span>
    ret, frame <span style="color: #333333">=</span> video_capture<span style="color: #333333">.</span>read()

    <span style="color: #888888"># Resize frame of video to 1/4 size for faster face recognition processing</span>
    small_frame <span style="color: #333333">=</span> cv2<span style="color: #333333">.</span>resize(frame, (<span style="color: #0000DD; font-weight: bold">0</span>, <span style="color: #0000DD; font-weight: bold">0</span>), fx<span style="color: #333333">=</span><span style="color: #6600EE; font-weight: bold">0.2</span>, fy<span style="color: #333333">=</span><span style="color: #6600EE; font-weight: bold">0.2</span>)

    <span style="color: #888888"># Convert the image from BGR color (which OpenCV uses) to RGB color (which face_recognition uses)</span>
    rgb_small_frame <span style="color: #333333">=</span> small_frame[:, :, ::<span style="color: #333333">-</span><span style="color: #0000DD; font-weight: bold">1</span>]

    <span style="color: #888888"># Find all the faces and face encodings in the current frame of video</span>
    new_face_locations <span style="color: #333333">=</span> face_recognition<span style="color: #333333">.</span>face_locations(rgb_small_frame)
    new_face_encodings <span style="color: #333333">=</span> face_recognition<span style="color: #333333">.</span>face_encodings(rgb_small_frame, new_face_locations)

    matched <span style="color: #333333">=</span> <span style="color: #007020">True</span>

    <span style="color: #888888"># Loop over each face found in the frame to see if it's someone we know.</span>
    <span style="color: #008800; font-weight: bold">for</span> new_face_encoding <span style="color: #000000; font-weight: bold">in</span> new_face_encodings:
        match <span style="color: #333333">=</span> face_recognition<span style="color: #333333">.</span>compare_faces(known_face_encodings, new_face_encoding)
        matched <span style="color: #333333">=</span> matched <span style="color: #000000; font-weight: bold">and</span> <span style="color: #007020">bool</span>(<span style="color: #007020">sum</span>(match))

    matched <span style="color: #333333">=</span> matched <span style="color: #000000; font-weight: bold">and</span> (<span style="color: #007020">len</span>(new_face_locations) <span style="color: #333333">&gt;</span> <span style="color: #0000DD; font-weight: bold">0</span>) <span style="color: #888888"># Make sure someone's there</span>


    <span style="color: #888888"># Depending on the state of matched, toggle the screen on or off</span>
    <span style="color: #008800; font-weight: bold">if</span> <span style="color: #000000; font-weight: bold">not</span> matched:
        <span style="color: #888888"># sp.run(["xset","dpms","force","off"]) # Debian, for Raspi</span>
        sp<span style="color: #333333">.</span>run([<span style="background-color: #fff0f0">"pmset"</span>, <span style="background-color: #fff0f0">"displaysleepnow"</span>]) <span style="color: #888888"># MacOS</span>
        turned_off <span style="color: #333333">=</span> <span style="color: #007020">True</span>
    <span style="color: #008800; font-weight: bold">elif</span> turned_off:
        turned_off <span style="color: #333333">=</span> <span style="color: #007020">False</span>
        <span style="color: #888888"># sp.run(["xset","dpms","force","on"]) # Debian, for Raspi</span>

        sp<span style="color: #333333">.</span>run([<span style="background-color: #fff0f0">"caffeinate"</span>, <span style="background-color: #fff0f0">"-u"</span>, <span style="background-color: #fff0f0">"-t"</span>, <span style="background-color: #fff0f0">"1"</span>]) <span style="color: #888888"># MacOS, has to run twice to be reliable</span>
        sp<span style="color: #333333">.</span>run([<span style="background-color: #fff0f0">"caffeinate"</span>, <span style="background-color: #fff0f0">"-u"</span>, <span style="background-color: #fff0f0">"-t"</span>, <span style="background-color: #fff0f0">"1"</span>])
</pre></td></tr></table></div>
<p><br/></p>
<p>That's all! Pretty simple program, but taught me some stuff about display power management and the trickiness of installing dlib. Also, writing this post taught me about HTML formatting (<a href="http://hilite.me/">hilite.me</a> is kinda nice)</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/privacyscreen/</guid>
      <pubDate>Wed, 09 Sep 2020 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Digital Holograms</title>
      <link>https://andykong.org/blog/digitalholo1/</link>
      <description>Displaying 3D images on a 2D computer screen</description>
      <content:encoded><![CDATA[<html><body><p>I was reading about holograms and realized that I could emulate it on a computer. Basically, screen objects don't shift perspective like real objects do (showing a different side when you look from the left vs. right, etc.) </p>
<p><img class="d-block mx-auto" src="/static/digitalholocube.png" width="400"/></p>
<p class="caption">[image credit](https://light2015blogdotorg.wordpress.com/2015/11/05/holography-art-with-light/)</p>
<p>However, with head tracking, the computer can compute what new angle your eyes are looking from and recalculate the image view. This can be used for looking at CAD models, or on websites for a more 3D experience, or for hiding information on the sides of a usually unviewable object (like a scavenger hunt or something), or creating a head whose eyes literally follow you around. </p>
<p>I made a little demo of this, and I'm going to do some more work with eye tracking in-browser soon. I know this concept has been done before, just not with a computer's built-in webcam.</p>
<blockquote class="twitter-tweet"><p dir="ltr" lang="en">Hologram displayed on flat computer screen <a href="https://t.co/efOuGUr61W">pic.twitter.com/efOuGUr61W</a></p>— Andy (@redlightguru) <a href="https://twitter.com/redlightguru/status/1271007318927241216?ref_src=twsrc%5Etfw">June 11, 2020</a></blockquote>
<script async="" charset="utf-8" src="https://platform.twitter.com/widgets.js"></script></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/digitalholo1/</guid>
      <pubDate>Thu, 18 Jun 2020 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Speed checking Python built-in functions</title>
      <link>https://andykong.org/blog/speedcheckpybuiltins/</link>
      <description>Calling str, type, etc. is free... right?</description>
      <content:encoded><![CDATA[<html><body><p>I found this cool <a href="http://blog.kevmod.com/2020/05/python-performance-its-not-just-the-interpreter">blog post</a> that was trying to show Python's slower execution time does not just come from its interpreter, and instead was more interested in the benchmark that they ran converting ints to strings as a toy example.</p>
<p>They mentioned off-hand that built-in function calls in Python must be traced back to the built-in library by the interpreter, which takes a decent amount of time if most of your code just calls built-in functions. Here's the code: </p>
<p><img class="d-block mx-auto" src="/static/speedcheckpybuiltinscode.png" width="400"/></p>
<h3>Results</h3>
<p>All we do is move the str() function into a local variable (allowed since Python is a functional language), but we gain 20-30% speed boost in the total execution times. </p>
<p><img class="d-block mx-auto" src="/static/speedcheckpybuiltinstimings.png" width="400"/></p>
<p>Of course, this is a toy example since we're only calling one function, but it might be worthwhile to precompile code like this if you're not uing Pypi or something. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/speedcheckpybuiltins/</guid>
      <pubDate>Mon, 25 May 2020 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Variable resistance tests of Velostat/Linqstat</title>
      <link>https://andykong.org/blog/velostatlinqstat/</link>
      <description>Testing the effects of bending, twisting, and touch on the conductive plastic Velostat.</description>
      <content:encoded><![CDATA[<html><body><p>This is <a href="https://en.wikipedia.org/wiki/Velostat">Velostat</a>:</p>
<p><img class="d-block mx-auto" src="/static/velostat.jpg" width="400"/></p>
<p>It's a conductive sheet that's sometimes used for packaging ESD-sensitive electronics, but is commonly used by wearables hobbyists since it changes resistance when <i>anything</i> happens to it. Wrinkling. Heating. Bending. Twisting. Pressing. Anything that touches it is detectable!</p>
<p>However, there's not a ton of information about it online, like how much change in resistance is visible for bending vs. twisting, etc. So I decide to do some testing and show y'all the results so you wouldn't have to!</p>
<p>All tests were done on a 6.0 x 1.2 cm piece of the material, which I got from Adafruit. I held it in contact using some alligator clips, which weren't super precise contacts. This is probably why the baseline resistances were so variable, around 55 kΩ ± 5 kΩ. </p>
<p><img class="d-block mx-auto" src="/static/velostatnormal2.jpg" width="400"/></p>
<p class="caption">Basic experimental setup</p>
<h3>Twisting</h3>
<p><img class="d-block mx-auto" src="/static/velostat180.jpg" width="400"/></p>
<p>For these tests, I simply flipped one alligator clip and held the strip taut on the table. Best results came from twisting the strip so much that it curled into a tube, around 360 degrees for this width of strip.</p>
<p><img class="d-block mx-auto" src="/static/velostattwist.png" width="400"/></p>
<h3>Bending</h3>
<p><img class="d-block mx-auto" src="/static/velostatbend.jpg" width="400"/></p>
<p>I bent the strip into a bit of an omega shape, which didn't change it much. When I pinched it, the overall distance of the strip changed so the resistance went down instead of up.</p>
<p><img class="d-block mx-auto" src="/static/velostatbend.png" width="400"/></p>
<h3>Pulling</h3>
<p>The resistance increased "linearly" from not pulling to maximum pull strength. This test was a bit tricky since my resistance would change the strips if I touched both alligator clips, so I carefully pulled both ends of the plastic without contacting the clips. </p>
<p><img class="d-block mx-auto" src="/static/velostatpull.png" width="400"/></p>
<h3>Touching/Pressure</h3>
<p>When it comes to pressure, Velostat is not that great of a sensor without modification. I found that surface area of the finger touch affects the resistance more than pressing on it, and that non-finger touching with a plastic scissor's handle did not affect it much at at all. </p>
<p><img class="d-block mx-auto" src="/static/velostattouch.png" width="400"/></p>
<h3>Touching (Bent sheet)</h3>
<p>Since I wanted to use this as a pressure sensor, I tried folding the sheet in the middle then pressing on that. This worked much better, but required securing by tape or some other adhesive. </p>
<p><img class="d-block mx-auto" src="/static/velostatfoldtouch.png" width="400"/></p>
<h3>Heating</h3>
<p><a href="https://electronics.stackexchange.com/questions/452291/velostat-sensitivity-to-temperature">This</a> site I visited claimed that heat did not affect the sheet's resistance, but I decided to test it out. I held a soldering iron near my test strip without touching it, and found the biggest changes yet! It took a few seconds to heat to the max resistance, and about a minute to cool back to baseline. </p>
<p><img class="d-block mx-auto" src="/static/velostatheating.png" width="400"/></p>
<h3>Connections and Conclusions</h3>
<p>I wanted to connect some wire to this stuff, but <a href="https://www.kobakant.at/DIY/?p=381">some people</a> online were using copper tape with conductive adhesive and conductive thread, which I didn't have. I tried soldering to it, but it just melted :(. Since alligator clips worked, I tried "crimping" some metal wire onto it, and that seemed to work well. Seems like it just needs to be in tight contact with metal to conduct. </p>
<p><img class="d-block mx-auto" src="/static/velostatcrimp.jpg" width="400"/></p>
<p>That's all! I'm probably going to stick with pressure sensing with my folded sheet, it seems to produce pretty drastic resistance changes. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/velostatlinqstat/</guid>
      <pubDate>Sun, 17 May 2020 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Speed Check</title>
      <link>https://andykong.org/blog/speedcheckwritingstrings/</link>
      <description>What's the fastest way to record a stream of strings in Python?</description>
      <content:encoded><![CDATA[<html><body><p>I'm trying to record a stream of string data coming from a Teensy microcontroller over USB Serial. I chose a high baud rate so I can transfer and save the data quickly, but I don't want to drop any elements from the stream while I'm trying to write it down. So I wrote a little test script to see which method is fastest. </p>
<h3>Results</h3>
<p>For each method, I wrote the string "yaga," 10,000 times. </p>
<p>Setting elements of a prealloc'd Python list works the fastest. I thought writing to a file would take less time, but it's actually around 3x slower. Python append works pretty fast too, but has the overhead of a list of changing size</p>
<p><img class="d-block mx-auto" src="/static/speedcheckStringWrite.png" width="400"/></p>
<p><a href="/static/speedcheckStringWrite.py">Link to code</a> </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/speedcheckwritingstrings/</guid>
      <pubDate>Sun, 10 May 2020 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Parts arrived for Altered Perceptions</title>
      <link>https://andykong.org/blog/alteredperceptions0/</link>
      <description>How would you like to see the world from a new pair of eyes?</description>
      <content:encoded><![CDATA[<html><body><p>Hello. This is Andy. I'll tell you about my new project, and why I need your help coming up with ideas for it. </p>
<p>Ever since I was young, I have wanted to be able to see the world a bit differently. I'm not sure if I had gotten bored of the way everything looked, or maybe because I knew that I wanted to see old things in a new light. Whether wishing I could change my reds for blues after hearing about "everyone sees colors differently" theory, or wishing I had a real life heads-up display to show me peoples names and subconscious emotions, I always wanted my eyes to be a bit more effective than they were. </p>
<p>Soon, I hope to make this a reality. Today I received a few boxes in the mail, containing a PlayStation VR, Jetson Nano, Raspicam, and some assorted cables. Can you tell where this is going? I'm going to run the PSVR from the Nano, and wear the Raspicam on the front of the headset to have a POV livestream that I can intercept and play with before sending to the wearer's eyes. The latency might be bad, and the video stream may not trick me into believing its "live live", but it'll emulate a childhood desire of mine. To see things differently with the press of a button. </p>
<p>We rely so heavily on our eyes for almost everything. Balance, trajectory planning, social interaction, building cool toys, playing video games. The list goes on and on. And soon, I will be able to have more than one pair. Say I wanted to up the contrast of the world, or overlay line detection over my view? Done in a few lines of code. What about the Minecraft nausea effect applied in real time, or even better, Deep Dreaming every frame I see? Easy enough given the level of control over the data that's available. For the first time, I will have granular control over how I see things. It will be glorious.</p>
<h2 id="if-you-find-this-project-interesting-andor-think-that-theres-some-neat-way-to-edit-the-visual-field-that-ive-missed-dont-hesitate-to-email-me-to-let-me-know-what-your-idea-is-and-ill-try-to-implement-it-andykongresearchgmailcom">If you find this project interesting and/or think that there's some neat way to edit the visual field that I've missed, don't hesitate to email me to let me know what your idea is, and I'll try to implement it (andykongresearch@gmail.com).</h2>
<p><br/><br/></p>
<ul>Filters/new eyes I'm trying to implement
    <li>rainbow cycling the colors</li>
<li>inverting the world</li>
<li>deepdreaming, live (like this video):</li>
</ul>
<iframe allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/DgPaCWJL7XI" width="560"></iframe></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/alteredperceptions0/</guid>
      <pubDate>Wed, 13 Nov 2019 00:00:00 -0500</pubDate>
    </item>
    <item>
      <title>Spin Tank Proof-of-Concept Test</title>
      <link>https://andykong.org/blog/spintank1/</link>
      <description>A new configuration for some old kinds of armor</description>
      <content:encoded><![CDATA[<html><body><p>The problem with modern day tank armor is that it relies too heavily on brute strength for defense. Armor can be made to actively resist attacks by means of a light, rapidly rotating hull that deflects projectiles instead of the heavy armor plates used today that simply endure every hit thrown at them.</p>
<h3>Modern tanks suck</h3>
<p><img class="d-block mx-auto" src="/static/spintankabrams.png"/></p>
<p>Tank armor must be strong enough to resist small arms fire (rifles, pistols, submachine guns) and stronger, penetrating projectiles (anti-tank missiles, SAMs). Thick depleted uranium plating forms the outer shell, and sheds small arms fire like water. This plating, in conjunction with ceramic armor and air pockets, also allows tank armor to redirect the focused explosions of anti-tank missiles. Anti-tank missiles first contact the tank, then use shaped charges to shoot a thin, fast-moving jet of plasma at the hull to melt a deep hole into the cockpit. On a normal tank, this stream goes straight the armor and must be redirected by the aforementioned air pockets to avoid injuring the crew.</p>
<p><img class="d-block mx-auto" src="/static/spintankantitankmissile.png"/></p>
<p>Not only is this armor dumb (not reactive), it is also super heavy because it takes every impact head-on. I couldn't find definitive stats on the armor weight, but it's designed to be thick and expendable. All these factors make the tank less manouverable than it could be, and more expensive than necessary. This doesn't have to be the case. </p>
<h3>Spin tank</h3>
<p>What if instead of the front-back-sides-bottom-top armor paradigm, we used one, rapidly-spinning shell as the armor? Keep it thick enough that small munitions won't get through, and let the hull's spinning knock away even perfectly-aimed anti-tank missiles. On the spin tank, in the time it takes the anti-tank missile to prime the plasma, the missile's contact point with the tank hull will have shifted along the perimeter. This means the shaped charges will no longer pointed in the right direction, and will detonate harmlessly, spraying plasma along the hull instead of into it. </p>
<p><img class="d-block mx-auto" src="/static/spintankmechanismdrawing.png"/></p>
<p class="caption">Missiles will just roll right off the spin tank.</p>
<p>You might be wondering what kind of rotation speeds are necessary to shed missiles like water. Good question. It depends on a series of factors including but not limited to: projectile mass, projectile velocity, tank hull thickness, tank hull strength, and projectile penetration strength. Unfortunately, I cannot currently simulate this in software, so you'll just have to settle for a hardware implementation!</p>
<p><img class="d-block mx-auto" src="/static/spintank3dmodel.png"/></p>
<p><img class="d-block mx-auto" src="/static/spintankmounted.png"/></p>
<p>I modeled a quick little dome that fits perfectly on a food processor I have, which spins almost too fast for my phone's built-in slo-mo to capture. Manually counting a mark on the rim, I got about 15 rotations per second. Not bad for a consumer appliance! </p>
<p>I've been using a chopstick in a slingshot to simulate a missile hitting the spin tank. When the dome is stationary, the chopstick has no problem penetrating it. However, the same shot on a spinning dome will leave a dent instead of a hole. </p>
<p><img class="d-block mx-auto" src="/static/spintankstationaryshot.png"/></p>
<p class="caption">Stationary chopstick firing leaves a hole</p>
<p><img class="d-block mx-auto" src="/static/spintankspinningshot.png"/></p>
<p class="caption">Spinning chopstick firing leaves a dent in the hull, but less damage</p>
<p>I need to experiment more with which latitude I'm shooting, since the hull strength changes at different heights. I should also use different lengths and weights of projectiles, since the chopstick is incredibly narrow compared to the dome itself. Some shots on the spinning hull also tore a larger hole than the one on the stationary hull, which I think is caused by a chopstick penetrating the hull followed by the rotation ripping it out.</p>
<p>At the very least, the spin tank reduces the total target size of the same tank, since hitting the edges of the spin tank with a projectile proves to be entirely useless.</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/spintank1/</guid>
      <pubDate>Tue, 20 Aug 2019 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Advanced addition and speedup of neural network evaluation</title>
      <link>https://andykong.org/blog/advancedaddition1/</link>
      <description>Linear functions and what it means for Neural Network acceleration</description>
      <content:encoded><![CDATA[<html><body><p>Hello! I've been doing some more work on the advanced addition project.</p>
<p><a href="https://andykong.org/blog/advancedaddition/">Last time</a>, I had just created some silly neural networks that added two numbers. The evaluation was trivial, but I was able to make the simple one explainable by boiling it down to the literal equation with the two inputs. I've made progress on this front.</p>
<p>I was able to break down the fully connected 2-10-1 neuron network (Model C) into its respective equation. Evaluated in order, this looks something like this:</p>
<p><img class="d-block mx-auto" src="/static/advancedaddition1symboliceqn.png" width="400"/></p>
<p>Since each variable is just a matrix of some size, and I've nicely arranged them in a way that allows multiplication, we can precompute some of the matrix multiplications for the final equation and arrive at a shortened form of the neural network for implementation purposes. You can think of this shortened form as a much easier model to evaluate for low power, discreet package sizes in remote areas without large computing pools. </p>
<p>The sizes of each variable are not exactly clear from this LaTeX equation, but trust me when I say its just simple matrix multiplication. Nothing fancy. Depending on the initial setup, the final equation for Model C reduces to something like this:</p>
<p><img class="d-block mx-auto" src="/static/advancedaddition1numericeqn.png" width="400"/></p>
<p>We can see that the equation is very similar to Model A's original equation from <a href="https://andykong.org/blog/advancedaddition/">this post</a>, despite Model C's <strong>30 multiplications</strong> and <strong>20 additions</strong> against Model A's <strong>two multiplications</strong> and <strong>two additions</strong>. This got me thinking; why can't we do this to every model? Why can't we precompute the matrix multiplications for every neural network, like those built for edge-computing on cameras or speakers?</p>
<p>I thought I was clever for a second, but then I realized that my models would only be able to approximate linear functions. The only features taken for input would be x<subscript>1</subscript>,x<subscript>2</subscript>,...x<subscript>n</subscript>, and they would just come out with some coefficient but no additional features. This is a formidable technique for simpler functions, but more complex hypotheses would require the activation functions (Sigmoid, Rectified Linear Unit, etc), which cannot be precomputed because they depend on the values of their inputs! </p>
<p>Activation functions could be broken down some more, but I would have to spend a little more time with it. I think that the individual nature of the function means that a simple precomputation wouldn't be allowed. We could threshold the inputs by backtracking the minimum value required to overcome the ReLU or something, and that would save a bit of time.</p>
<p>So, the takeaway is that most neural networks cannot be sped up using precomputation, since every layer usually has a nonlinear activation function. But in any model with multiple matrix multiplications or additions done in succession, the evaluation of the two matrices can be precomputed to speed up the model. </p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/advancedaddition1/</guid>
      <pubDate>Thu, 01 Aug 2019 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>The miracle of brain-computer interfaces</title>
      <link>https://andykong.org/blog/extraarms/</link>
      <description>How BCIs can give us more arms</description>
      <content:encoded><![CDATA[<html><body><p>Bear with me. I'm going to try to explain something slightly abstract but incredibly interesting. </p>
<h2 id="you-the-brain-reading-this">You, the brain reading this</h2>
<p>Look at your hands. Admire your reflection in a mirror. It's you, right?</p>
<p>Wrong. That is not you. You are the brain inside that person. You are a chunk of meat piloting a bone-armored meatsuit, and that meatsuit's hands and feet are the only ways you can interact with the physical world. It is kinda weird to think of our limbs as tools. Our hands act as an extension of our brain into the physical world, a way for our abstract thoughts to influence the concrete world. And it's our best tool because it's so generalizable; I'm currently using the fingers on mine to hit my keyboard in specific combinations to write the words you're reading. </p>
<p>However, their generality leads us to create more specialized tools to make certain tasks easier for us. Hammers allow us to piece together pieces of wood with bits of metal. Pianos make it easy to create specific sounds reliably. Keyboards enable precise electron changes on tiny, tiny silicon chips. We use our general tools to create and harness more specialized ones. </p>
<p>But there's a problem. Whenever we use a tool, we are actually trading use of our hands or feet for use of a more specialized tool. Even if we know exactly how we want to use the tool, we have to figure out how to best interact with it. This is an added learning curve called muscle memory.</p>
<p>This might not seem like a big deal to most people, but that's because most people have working hands or feet. They have general tools with which to harness specialized ones. But what if you're already using both your hands and just need another one, like a surgeon in a 36 hour surgery who just needs a nurse to hold a piece of skin out of the way? What if you've lost the ability to control your limbs in bike accident, or have terribly trembling hands from Parkinson's as a result of a random mutation? These people can't harness any tools! They're out of luck in today's technology.</p>
<p><img class="d-block mx-auto" src="/static/extraarmeeg.jpg" width="450"/></p>
<p class="caption"></p>
<h2 id="not-just-a-better-keyboard">Not just a better keyboard</h2>
<p>This is where brain-computer interfaces completely dominate current, existing interfaces. While a joystick might allow us to replace our arms with a more powerful and steady robotic arm, a brain-computer interface allows us to use a robotic arm <em>in addition to the two incredibly versatile arms we already have</em>. BCIs aren't just better keyboards; they're the only tool that doesn't require us to give up one of our general tools to use a more specific one. </p>
<p>I'm not saying BCIs will allow us all to be Doc Ock, or that they'll cure disability forever. There'll still be a learning curve, just like for learning to type. But inputs can now occur at the speed of thought, instead of being limited at the speed at which we can move our fingers. We can connect our brains and interact directly with the world around us, instead of having to go through our muscles as middlemen. </p>
<h2 id="what-it-means-for-us">What it means for us</h2>
<p>When computers first became commercially available in the 1960s, they revolutionized the way we did work. Calculations could be done instantly; large spreadsheets of data could be manipulated at scale. But today, despite all the advances we've made in computing, we do work at the same rate as a worker in the 1960s. We're limited fundamentally by the speed of our interface, the keyboard and mouse.</p>
<p>BCIs are currently still in the early stages of development, and won't be feasible for at least another 10 years. But with a working brain-computer interface, we'll open an entirely new field of ways to connect our brains to the world around us. It's time for a new interface. </p>
<p><br/><br/></p>
<p>in the bent get get</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/extraarms/</guid>
      <pubDate>Sun, 28 Jul 2019 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>Simple arithmetic addition with neural networks</title>
      <link>https://andykong.org/blog/advancedaddition/</link>
      <description>I have stumbled across a new way to add numbers.</description>
      <content:encoded><![CDATA[<html><body><p>Before we start, I want to clarify that I am not just some neural network script kiddie, and that my blog is not going to be just a personal log of all the neural networks I make. I also enjoy electronics and hardware design. That being said, check out this neural network I made!</p>
<p>There is an underlying perception that people new to the topic of machine learning have, and that is this: when a model isn't doing too hot, you need either more data or more hidden layers with more neurons between them. This misconception isn't their fault; it seems everyone in the news is talking about these huge neural networks trained on terabytes of data to decide whether to advertise an eco-friendly backpack or water bottle to an unsuspecting consumer based on their heart rate variance. However, more neurons != better fit, as I will proceed to show. </p>
<p>We often think of neural networks as endlessly flexible tools. I mean, can't they <a href="https://en.wikipedia.org/wiki/Universal_approximation_theorem">approximate any known function</a>? And while that is mathematically proven to be true, you, as a real-world practitioner of ML, have to consider how much more complicated the cost function gets for every single neuron you add. The already-bumpy landscape of a 3D cost function becomes more wrinkly, and your model has a much higher chance of getting stuck in a local minima. </p>
<p>This becomes apparent when you train a neural network to do something as simple as addition. I generated 1000 examples of addition of two integers between 0-100, which looks like this</p>
<p><img class="d-block mx-auto" src="/static/advancedadditiontraining.png" width="300"/></p>
<h2 id="model-a">Model A</h2>
<p>Using these, I trained a simple neural network with no hidden layers (does that invalidate it as a neural network? I'm not sure the semantics of the name), 2 input nodes, and 1 output node. Let's call it Model A. Here's the results</p>
<p><img class="d-block mx-auto" src="/static/aasimpleexamples.png" width="300"/></p>
<p>As you can see, this model is a little off, but asymptotically correct. The error is nearly constant regardless of the inputs, meaning as the inputs go up, the total percentage error of the model goes to 0. In addition, this model only requires 2 multiplications and 2 additions to perform one addition, which is only a 4x cost computationally. Since our computers are definitely more than 4x faster than they were when addition was invented, this model is feasible for use in modern industry. </p>
<p>Even better, this example of machine learning is explainable. If we take a look at the weights, we can boil this neural network down into one equation. Using x<sub>1</sub> and x<sub>2</sub> as the first and second inputs respectively, our neural network's weights can be algebraically expressed.</p>
<p><img class="d-block mx-auto" src="/static/aasimpleweights.png" width="300"/>
Becomes
<img class="d-block mx-auto" src="/static/aaboiled.png" width="300"/></p>
<p>This AI model is clearly understandable by any person who knows basic algebra, and therefore can be trusted to perform addition in a transparent method that doesn't involve plotting to kill its human creators with some of its spare neurons. </p>
<h2 id="model-b">Model B</h2>
<p>Now, contrast this simple but effective Model A with a second, more complex Model B. This model has a hidden layer with one neuron in it, in addition to the input 2 nodes and output node. As such, it performs worse addition. </p>
<p><img class="d-block mx-auto" src="/static/advancedadditionexamples.png" width="300"/></p>
<p>We see that the error is significant, definitely not enough to just round away like we could with Model A. This model also took extra time to train, since it had an additional 3 equations to calculate. It is significantly less feasible for use in industry because of its inefficiency and inaccuracy. </p>
<h2 id="model-c">Model C</h2>
<p>"But what about huge inner layers Andy? You're just using one! Won't that solve all our problems like Elon Musk and Andrew Ng promised?" <strong>NO!</strong> Here I've created Model C, which has 10 hidden neurons in its hidden layer. </p>
<p><img class="d-block mx-auto" src="/static/aabignetexamples.png" width="300"/></p>
<p>It's AWFUL! Terrible! Abysmal! A child could do better than that! Whe- oh, we're getting a message from ML themselves. They said to change the learning rate. Whoops. Here's the actual results</p>
<p><img class="d-block mx-auto" src="/static/aabignetgoodexamples.png" width="300"/></p>
<p>We can see that though Model C performs better than Model B's small hidden layer, it's doesn't even beat Model A, the simplest model of them all. And additionally, the computation cost is through the roof! Definitely not scalable for industry. </p>
<p>The takeaway of this is that not all machine learning problems can be solved by throwing tons of neurons at them, in the same way that not all real world problems can be solved by throwing tons of ninjas at them (but most of them could). So before you jam 16 fully-connected hidden layers of 256 neurons each in your neural network, think about how complex the function you're trying to model actually is. And pick a model that fits the function you need, instead of one that fits your mental model of how chunky thicc a SOTA neural network should be. You might get better results for less computation.</p>
<hr/>
<p>Bonus: Addition using any of these models is not commutative, which was really funny to me. I think this has applications for curing the boredom once mathematicians and kindergarteners get tired of adding 1 and 1 together. They can now do it in new ways!</p>
<p><img class="d-block mx-auto" src="/static/aanoncommutativity.png" width="300"/></p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/advancedaddition/</guid>
      <pubDate>Wed, 17 Jul 2019 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>The exploration of unexplorable search spaces</title>
      <link>https://andykong.org/blog/unexplorable/</link>
      <description>Any hard problem can be solved by randomly guessing — if you're good enough at guessing.</description>
      <content:encoded><![CDATA[<html><body><p>Any hard problem can be solved by randomly guessing — if you're good enough at guessing.</p>
<p>Take my website's aesthetic. The colors composing my website were not chosen at random. I went on a nice site called <a href="https://www.colourlovers.com/">colourlovers.com</a> where people discover and post colors they think are beautiful and I found a curated selection of the best colors out of the 16.7 million RGB ones. Not only that, they were already matched in a palette of 4 complementary colors! Thank god for these color-space explorers, without them my site would be a garish combination of 4 colors I liked individually, but put together look like an alternative Mardi Gras parade.</p>
<p><img class="d-block mx-auto" src="/static/dreammagnet.png" width="450"/></p>
<p class="caption">[Dream Magnet](https://www.colourlovers.com/palette/482774/dream_magnet) has one of the prettiest cyans I have ever seen</p>
<p>That incident got me thinking. I think it is amazingly frustrating that many problems have solutions that can be guessed. In any field, almost all problems can be randomly, instantaneously solved to a greater extent than methodical approaches are currently solving them. This is because we know what type of thing we're guessing (integers from 1-60), just not what the right guesses are.</p>
<p>In the field of personal finance, I could correctly guess 4 or 5 numbers and win the lottery. The solution space for this one isn't even that big, only 60<sup>5</sup>=777 million (I wonder if the lucky numbers are intentional), but the importance is several orders of magnitude higher than picking a color scheme for my website while only being 1 order of magnitude harder to find.  </p>
<p>Or take machine learning for instance. Nowadays, modern research labs spent millions training neural networks with their fancy computers. With the invention of <a href="http://www.iro.umontreal.ca/~pift6266/A06/refs/backprop_old.pdf">backpropagation</a> by Geoffrey Hinton in 1986, every school and company dropped anything computationally challenging they were working on (re: not webdev or IT) to figure out the fastest way to do matrix math really, really fast. The faster your model could multiply huge matrices of floating-point numbers together, the better/faster/stronger it could tell a <a href="https://www.kaggle.com/c/dogs-vs-cats">dog from a cat</a> or <a href="http://www.image-net.org/challenges/LSVRC/">classify a 32x32 pixel image</a> as a airplane, bird, etc.</p>
<p>Backpropagation left the machine learning community a method for tuning the matrices. As any high school/college kid interested in machine learning knows, the really hard part of machine learning that Geoffrey Hinton didn't solve is <del>automagically importing data</del> twiddling the matrix numbers to perfection sometime before the heat death of the universe. THAT'S why my neighbor at my first-year college dorm had multiple high-end graphics cards, not to play the Overwatch in 4K at 144Hz while their model is training. My jealousy of their dual 144Hz monitor gaming setup aside, finding the minimum in higher dimensions is HARD. </p>
<p><img class="mx-auto d-block" src="https://cdn-images-1.medium.com/max/1600/1*f9a162GhpMbiTVTAua_lLQ.png" style="" width="300px"/></p>
<p class="caption">You think this is bad? Imagine how many bumps it has in the 500th dimension!!</p>
<p>Confronted with all this complexity from today's approach, I could just give up. Like an Amazon warehouse stocking inventory, I could just begin to guess values and randomly shove them into the matrices anywhere they fit. And, in one world out of many I will achieve a miracle: I will find the global minimum by raw chance. Now, there's no academic clout to be gained this way, and I wouldn't know how my matrices worked. But neither does any other ML researcher. </p>
<p>Then take a look at medical research. To find new medicines today, we just add random functional groups to an existing, working drug. To check if doing something random made the medicine more effective somehow, we give it to a bunch of rats, and if it works for them, a bunch of monkeys, and if it works for them, the humans that need it. The process takes millions of dollars and lots of time, and in the end we still don't know how the new drug works until some PhD student figures out the mechanism of action 20 years later. </p>
<p>Why does it matter? It's because I think it's sad that the lives of many experts today will be dedicated to finding the best way to guess at random numbers, with no need to understand <em>how it all works</em> once they've done it.</p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/unexplorable/</guid>
      <pubDate>Sun, 30 Jun 2019 00:00:00 -0400</pubDate>
    </item>
    <item>
      <title>First Post!</title>
      <link>https://andykong.org/blog/first/</link>
      <description>WHAT'S GOING ON I'M MAKING A WEBSITE</description>
      <content:encoded><![CDATA[<html><body><p><strong>WHAT'S GOING ON I'M MAKING A WEBSITE</strong></p></body></html>]]></content:encoded>
      <guid isPermaLink="true">https://andykong.org/blog/first/</guid>
      <pubDate>Mon, 24 Jun 2019 00:00:00 -0400</pubDate>
    </item>
  </channel>
</rss>
