<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://nalbam.github.io//feed.xml" rel="self" type="application/atom+xml" /><link href="https://nalbam.github.io//" rel="alternate" type="text/html" /><updated>2026-02-23T05:11:40+00:00</updated><id>https://nalbam.github.io//feed.xml</id><title type="html">nalbam’s blog</title><subtitle>Well, here it is</subtitle><entry><title type="html">Setting Up VibeMon on ESP32: WiFi Configuration in 3 Steps</title><link href="https://nalbam.github.io//2026/02/23/vibemon-setup.html" rel="alternate" type="text/html" title="Setting Up VibeMon on ESP32: WiFi Configuration in 3 Steps" /><published>2026-02-23T00:00:00+00:00</published><updated>2026-02-23T00:00:00+00:00</updated><id>https://nalbam.github.io//2026/02/23/vibemon-setup</id><content type="html" xml:base="https://nalbam.github.io//2026/02/23/vibemon-setup.html"><![CDATA[<p><img src="/assets/images/2026-02-23/startup.jpg" alt="VibeMon Startup Screen" /></p>

<h2 id="getting-your-esp32-ready-to-monitor-ai">Getting Your ESP32 Ready to Monitor AI</h2>

<p>In my <a href="/2026/02/05/vibemon-en.html">previous post</a>, I covered how VibeMon was built — from a tiny LCD screen idea to a full cloud-connected AI status monitor. Today, let’s walk through the actual setup process for the ESP32 hardware.</p>

<p>The goal is simple: power on the device, connect it to your WiFi, and let it start receiving live status updates from VibeMon.</p>

<hr />

<h2 id="step-1-power-on--the-device-broadcasts-its-own-wifi">Step 1: Power On — The Device Broadcasts Its Own WiFi</h2>

<p>Plug the ESP32 into a USB power source. Within a few seconds, you’ll see a <strong>random VibeMon character</strong> appear on the tiny LCD screen.</p>

<p><img src="/assets/images/2026-02-23/startup.jpg" alt="VibeMon Startup" /></p>

<blockquote>
  <p>The character shown here is <strong>OpenClaw (claw)</strong> — one of the three supported AI agent characters. Which character appears on first boot is randomized.</p>
</blockquote>

<p>Along with the character, the screen shows the credentials for its built-in WiFi access point:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SSID:     VibeMon-Setup
Password: vibemon123
</code></pre></div></div>

<p>The ESP32 is now acting as its own WiFi hotspot, waiting for you to connect and configure it.</p>

<hr />

<h2 id="step-2-connect-to-vibemon-setup">Step 2: Connect to VibeMon-Setup</h2>

<p>On your Mac (or any WiFi-capable device), open the WiFi menu and look for the <strong>VibeMon-Setup</strong> network.</p>

<p><img src="/assets/images/2026-02-23/wifi-setup.png" alt="WiFi Network List" /></p>

<p>Select <strong>VibeMon-Setup</strong>, then enter the password when prompted:</p>

<p><img src="/assets/images/2026-02-23/wifi-password.png" alt="WiFi Password Entry" /></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Password: vibemon123
</code></pre></div></div>

<p>Once connected, your device is now talking directly to the ESP32 over its local network.</p>

<hr />

<h2 id="step-3-configure-wifi-and-token-via-the-setup-page">Step 3: Configure WiFi and Token via the Setup Page</h2>

<p>After connecting, your device will trigger a <strong>captive portal</strong> — the browser opens automatically. If it doesn’t, navigate to <code class="language-plaintext highlighter-rouge">http://192.168.4.1</code> manually.</p>

<blockquote>
  <p><strong>Using a smartphone?</strong> Make sure to disable mobile data before connecting. Otherwise your phone may route traffic through cellular and the captive portal won’t appear.</p>
</blockquote>

<p>You’ll see the <strong>VibeMon Setup</strong> page:</p>

<p><img src="/assets/images/2026-02-23/vibemon-setup.png" alt="VibeMon Setup Page" /></p>

<p>The setup page lets you configure three things:</p>

<table>
  <thead>
    <tr>
      <th>Field</th>
      <th>Required</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>WiFi SSID</strong></td>
      <td>✅</td>
      <td>Search and select your home/office WiFi network</td>
    </tr>
    <tr>
      <td><strong>WiFi Password</strong></td>
      <td>✅</td>
      <td>Password for the selected WiFi</td>
    </tr>
    <tr>
      <td><strong>VibeMon Token</strong></td>
      <td>Optional</td>
      <td>Your token from <a href="https://vibemon.io">vibemon.io</a> to receive live status updates</td>
    </tr>
  </tbody>
</table>

<h3 id="selecting-your-wifi">Selecting Your WiFi</h3>

<p>The page scans nearby networks and lists them as selectable options. Just tap or click your network name — no need to type the SSID manually. If your network doesn’t appear, move closer to the router and hit <strong>Rescan</strong>.</p>

<h3 id="vibemon-token-optional-but-recommended">VibeMon Token (Optional but Recommended)</h3>

<p>Without a token, the device connects to WiFi but won’t receive any status data. To get a token:</p>

<ol>
  <li>Visit <a href="https://vibemon.io">vibemon.io</a></li>
  <li>Sign in and generate a token</li>
  <li>Paste it into the <strong>VibeMon Token</strong> field</li>
</ol>

<blockquote>
  <p>If you skip the token for now, you can always re-enter setup mode by holding the reset button on the ESP32.</p>
</blockquote>

<hr />

<h2 id="what-happens-after-you-submit">What Happens After You Submit</h2>

<p>Once you fill in the fields and hit <strong>Save</strong>, the ESP32:</p>

<ol>
  <li><strong>Restarts</strong> automatically</li>
  <li><strong>Connects to your WiFi</strong> network using the credentials you provided</li>
  <li><strong>Establishes a WebSocket connection</strong> to VibeMon’s cloud server</li>
  <li><strong>Starts receiving status updates</strong> — and displaying them on screen in real time</li>
</ol>

<p>The LCD will show your AI agent’s current state: thinking, working, idle, or done — all reflected as pixel art animations on the little screen.</p>

<p>The entire process — from power-on to connected — takes <strong>under 30 seconds</strong> in total.</p>

<table>
  <thead>
    <tr>
      <th>Phase</th>
      <th>Expected Time</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Boot into provisioning mode</td>
      <td>&lt; 5 seconds</td>
    </tr>
    <tr>
      <td>WiFi network scan</td>
      <td>3–8 seconds</td>
    </tr>
    <tr>
      <td>Credential save</td>
      <td>&lt; 3 seconds</td>
    </tr>
    <tr>
      <td>WiFi connection after reboot</td>
      <td>5–15 seconds</td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p>Credentials (WiFi SSID, password, and VibeMon Token) are saved to the ESP32’s <strong>NVS (Non-Volatile Storage)</strong>, so they survive reboots and power cycles.</p>
</blockquote>

<hr />

<h2 id="troubleshooting">Troubleshooting</h2>

<p><strong>Can’t find VibeMon-Setup in the WiFi list?</strong></p>
<ul>
  <li>Make sure the ESP32 is powered on and the screen shows the setup credentials</li>
  <li>Try moving closer to the device</li>
</ul>

<p><strong>Setup page doesn’t open automatically?</strong></p>
<ul>
  <li>Manually navigate to <code class="language-plaintext highlighter-rouge">http://192.168.4.1</code> in your browser</li>
  <li>On smartphones, disable mobile data before connecting — cellular traffic can prevent the captive portal from appearing</li>
</ul>

<p><strong>Network not showing up in the scan list?</strong></p>
<ul>
  <li>Move closer to your router and tap <strong>Rescan</strong></li>
  <li>Verify the router is broadcasting its SSID (not hidden)</li>
</ul>

<p><strong>Device isn’t connecting to WiFi after setup?</strong></p>
<ul>
  <li>Double-check that your WiFi password was entered correctly</li>
  <li>After 20 failed connection attempts, the device will automatically clear its saved credentials and reboot back into provisioning mode — just reconfigure from Step 1</li>
</ul>

<p><strong>Need to reset WiFi settings without touching the device?</strong></p>
<ul>
  <li>If the device is already connected to your network, send a POST request to <code class="language-plaintext highlighter-rouge">/wifi-reset</code></li>
  <li>This clears saved WiFi credentials (while keeping the VibeMon Token) and reboots into provisioning mode</li>
</ul>

<p><strong>Device isn’t connecting to VibeMon after setup?</strong></p>
<ul>
  <li>Confirm your VibeMon Token is valid at <a href="https://vibemon.io">vibemon.io</a></li>
  <li>Re-enter setup mode and re-enter the token</li>
</ul>

<p><strong>Security note:</strong> The default AP password <code class="language-plaintext highlighter-rouge">vibemon123</code> is publicly known. Complete the WiFi configuration quickly after first boot to minimize exposure.</p>

<hr />

<h2 id="thats-it">That’s It!</h2>

<p>Three steps: power on, connect, configure. Once your ESP32 is on your local network and linked to VibeMon, it quietly sits on your desk and tells you exactly what your AI coding assistant is up to — without you having to glance at the terminal.</p>

<hr />

<p><strong>Links:</strong></p>
<ul>
  <li>Dashboard: <a href="https://vibemon.io">vibemon.io</a></li>
  <li>Desktop App: <a href="https://www.npmjs.com/package/vibemon">npm - vibemon</a></li>
  <li>GitHub: <a href="https://github.com/nalbam/vibemon">nalbam/vibemon</a>, <a href="https://github.com/nalbam/vibemon-app">nalbam/vibemon-app</a></li>
  <li>Hardware: <a href="https://aliexpress.com/item/1005008465501661.html">ESP32-C6-LCD-1.47</a></li>
</ul>]]></content><author><name></name></author><category term="vibemon" /><category term="esp32" /><category term="arduino" /><category term="wifi" /><category term="setup" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Making VibeMon: Showing AI Coding Assistant Status with Cute Pixel Art</title><link href="https://nalbam.github.io//2026/02/05/vibemon-en.html" rel="alternate" type="text/html" title="Making VibeMon: Showing AI Coding Assistant Status with Cute Pixel Art" /><published>2026-02-05T00:00:00+00:00</published><updated>2026-02-05T00:00:00+00:00</updated><id>https://nalbam.github.io//2026/02/05/vibemon-en</id><content type="html" xml:base="https://nalbam.github.io//2026/02/05/vibemon-en.html"><![CDATA[<p><img src="/assets/images/2026-02-05/demo.gif" alt="VibeMon Demo" /></p>

<h2 id="it-started-with-a-small-lcd-screen">It Started with a Small LCD Screen</h2>

<p>January 2026. I wanted a small LCD display for my desk. An ESP32 with a 172×320 resolution screen. What should I show on it?</p>

<p>While coding with Claude Code, it hit me: “What if I could show what the AI is doing right now on this screen?”</p>

<p>I couldn’t keep staring at the terminal window, and I never knew when tasks would finish. So I started — building a monitor that shows at a glance what the AI coding assistant is doing.</p>

<h2 id="phase-1-esp32-prototype-january-24">Phase 1: ESP32 Prototype (January 24)</h2>

<p>I started development before the ESP32 device arrived. Initially called “Claude Code Status Display.” The code is now available in the <a href="https://github.com/nalbam/vibemon-app">vibemon-app</a> repository. The goal was simple:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Claude in thinking state → Show "Thinking..." on screen
Claude in working state → Show "Working..." on screen
</code></pre></div></div>

<p>But just showing text would be boring, right? So I added <strong>pixel art characters</strong>.</p>

<p>Starting with eye shapes, I created expressions for each state:</p>
<ul>
  <li><strong>idle</strong> state: ■ ■ (default eyes)</li>
  <li><strong>thinking</strong> state: ▀ ▀ + 💭 (thought bubble)</li>
  <li><strong>working</strong> state: 🕶️ (sunglasses - Matrix style!)</li>
  <li><strong>notification</strong> state: ● ● + ? (question mark)</li>
  <li><strong>done</strong> state: &gt; &lt; (satisfied eyes)</li>
</ul>

<h2 id="phase-2-simulator-and-desktop-app-january-24---february-1">Phase 2: Simulator and Desktop App (January 24 - February 1)</h2>

<p>I needed a way to test before the ESP32 arrived. So I built a <strong>web simulator</strong> first. To preview the ESP32 screen in a browser.</p>

<p>While waiting, I also built a <strong>Desktop App</strong>. This Electron app became an alternative for those without ESP32 hardware.</p>

<p><strong>Features were rapidly added:</strong></p>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Always on Top</td>
      <td>Always displayed above other windows</td>
    </tr>
    <tr>
      <td>System Tray</td>
      <td>Quick control from menu bar</td>
    </tr>
    <tr>
      <td>Frameless Window</td>
      <td>Clean floating design</td>
    </tr>
    <tr>
      <td>Floating Animation</td>
      <td>Gentle bobbing effect</td>
    </tr>
  </tbody>
</table>

<p>And integration with <strong>Claude Code hooks</strong>! Status automatically sent to the app whenever Claude Code changes state.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Send status via Claude Code hooks</span>
curl <span class="nt">-X</span> POST http://127.0.0.1:19280/status <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{"state":"working","tool":"Bash","project":"my-project"}'</span>
</code></pre></div></div>

<h2 id="evolution-of-connection-methods">Evolution of Connection Methods</h2>

<p>The way Claude Code and VibeMon connect kept evolving.</p>

<h3 id="stage-1-local-http">Stage 1: Local HTTP</h3>

<p>Started simple. Direct transmission from Claude Code hooks to Desktop App.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> POST http://127.0.0.1:19280/status <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{"state":"working"}'</span>
</code></pre></div></div>

<h3 id="stage-2-usb-serial">Stage 2: USB Serial</h3>

<p>The ESP32 arrived! <a href="https://aliexpress.com/item/1005008465501661.html"><strong>ESP32-C6-LCD-1.47</strong></a> board — a cute device with a small 172×320 resolution LCD.</p>

<p><img src="/assets/images/2026-02-05/esp32_and_desktop.jpg" alt="ESP32 and Desktop App" /></p>

<p>It was my first time using Arduino, but AI made it easy. Asked Claude Code and got a guide instantly.</p>

<p><strong>Arduino IDE Setup:</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. Install Arduino IDE
2. Add ESP32 board manager (Preferences → Additional Board URLs)
   https://espressif.github.io/arduino-esp32/package_esp32_index.json
3. Select board: ESP32C6 Dev Module
4. Partition scheme: Huge APP (3MB No OTA/1MB SPIFFS)  ← Required for SSL!
</code></pre></div></div>

<p><strong>Required Libraries:</strong></p>

<table>
  <thead>
    <tr>
      <th>Library</th>
      <th>Purpose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>LovyanGFX</td>
      <td>TFT display control (ESP32-C6 compatible)</td>
    </tr>
    <tr>
      <td>ArduinoJson</td>
      <td>JSON parsing</td>
    </tr>
    <tr>
      <td>WebSockets</td>
      <td>WebSocket client</td>
    </tr>
  </tbody>
</table>

<p>Build and upload! Connect via USB cable and status transmits via serial communication.</p>

<h3 id="stage-3-internal-network-http">Stage 3: Internal Network HTTP</h3>

<p>ESP32 connects to WiFi and runs a web server anyway, right? Just send via HTTP on the same network without USB cable.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> POST http://192.168.1.xxx/status <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{"state":"working"}'</span>
</code></pre></div></div>

<h3 id="stage-4-cloud-websocket">Stage 4: Cloud WebSocket</h3>

<p>Supporting OpenClaw made me ambitious. I wanted to see the status of all AI agents from home and office on <strong>one screen</strong>.</p>

<p>So I registered the <strong>vibemon.io</strong> domain and built a web dashboard with WebSocket server. Now I can monitor in real-time from anywhere.</p>

<p>OpenClaw could install VibeMon via a <strong>plugin system</strong>. Unlike Claude Code’s hooks, OpenClaw uses a plugin system to add extensions. Eventually, I made OpenClaw install it by itself too — a meta situation where the AI installs and configures the plugin.</p>

<h3 id="hidden-challenge-model-and-memory">Hidden Challenge: model and memory</h3>

<p>Claude Code hooks provide <code class="language-plaintext highlighter-rouge">state</code>, <code class="language-plaintext highlighter-rouge">tool</code>, <code class="language-plaintext highlighter-rouge">project</code> information, but <strong>model</strong> and <strong>memory</strong> information isn’t directly available.</p>

<p>Wouldn’t it be much more useful to see this on screen? Which model is being used, how much context window is consumed.</p>

<p>The solution was <strong>statusline</strong>. Claude Code’s statusline shows model and memory information. So I created <code class="language-plaintext highlighter-rouge">statusline.py</code>:</p>

<ol>
  <li>Parse model, memory info from statusline</li>
  <li>Save to cache file per project (<code class="language-plaintext highlighter-rouge">~/.claude/vibemon/{project}.json</code>)</li>
  <li>Hook reads from cache and sends data together</li>
</ol>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># statusline.py - Save model/memory info to cache
</span><span class="n">cache</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"model"</span><span class="p">:</span> <span class="s">"Opus 4.5"</span><span class="p">,</span>
    <span class="s">"memory"</span><span class="p">:</span> <span class="mi">45</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># hook - Read from cache and send
</span><span class="n">status</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"state"</span><span class="p">:</span> <span class="s">"working"</span><span class="p">,</span>
    <span class="s">"tool"</span><span class="p">:</span> <span class="s">"Bash"</span><span class="p">,</span>
    <span class="s">"project"</span><span class="p">:</span> <span class="s">"vibemon"</span><span class="p">,</span>
    <span class="s">"model"</span><span class="p">:</span> <span class="n">cache</span><span class="p">[</span><span class="s">"model"</span><span class="p">],</span>    <span class="c1"># Value from statusline
</span>    <span class="s">"memory"</span><span class="p">:</span> <span class="n">cache</span><span class="p">[</span><span class="s">"memory"</span><span class="p">]</span>   <span class="c1"># Value from statusline
</span><span class="p">}</span>
</code></pre></div></div>

<p>This way, two scripts cooperate to send complete status information.</p>

<p>On the other hand, <strong>Kiro</strong> had no way to get model and memory information at all. No statusline-like feature, and hook-provided information is limited. So the Kiro character only shows state, tool, and project. Unfortunate, but unavoidable.</p>

<h2 id="phase-3-multi-project-support-late-january">Phase 3: Multi-Project Support (Late January)</h2>

<p>What about working on multiple projects simultaneously? → <strong>Multi-window mode</strong> was born!</p>

<p><img src="/assets/images/2026-02-05/desktop_multi_windows.png" alt="Multi-window Mode" /></p>

<p>Each project gets its own window, with active projects on the right and inactive on the left, auto-arranged. Supports up to 5 windows.</p>

<h2 id="phase-4-web-dashboard-february-1">Phase 4: Web Dashboard (February 1)</h2>

<p>Using only locally felt limiting. I wanted to see AI agent status from multiple locations in one place. So the <a href="https://github.com/nalbam/vibemon">vibemon</a> web dashboard project started!</p>

<h3 id="tech-stack">Tech Stack</h3>

<ul>
  <li><strong>Frontend</strong>: Next.js 16 + React 19 + TypeScript</li>
  <li><strong>Database</strong>: AWS DynamoDB (Single Table Design)</li>
  <li><strong>Real-time</strong>: WebSocket</li>
  <li><strong>Infrastructure</strong>: Terraform (API Gateway, Lambda)</li>
  <li><strong>Hosting</strong>: AWS Amplify</li>
</ul>

<h3 id="architecture">Architecture</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────────────────────────────────────────────┐
│                           VibeMon Architecture                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐                      │
│  │ Claude Code │    │    Kiro     │    │  OpenClaw   │                      │
│  │   (clawd)   │    │   (kiro)    │    │   (claw)    │                      │
│  └──────┬──────┘    └──────┬──────┘    └──────┬──────┘                      │
│         │                  │                  │                             │
│         │   Hooks          │   Hooks          │                             │
│         └──────────────────┼──────────────────┘                             │
│                            ▼                                                │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                      AWS Amplify (Hosting)                          │    │
│  │  ┌───────────────────────────────────────────────────────────────┐  │    │
│  │  │              Next.js 16 + React 19 + TypeScript               │  │    │
│  │  │  ┌─────────────────────┐    ┌─────────────────────────────┐   │  │    │
│  │  │  │   POST /api/status  │    │   GET /api/statuses,metrics │   │  │    │
│  │  │  │  (Bearer Token Auth)│    │                             │   │  │    │
│  │  │  └──────────┬──────────┘    └──────────────┬──────────────┘   │  │    │
│  │  └─────────────┼──────────────────────────────┼──────────────────┘  │    │
│  └────────────────┼──────────────────────────────┼─────────────────────┘    │
│                   │                              │                          │
│                   ▼                              ▼                          │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │                    DynamoDB (Single Table Design)                  │     │
│  │  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐  │     │
│  │  │  Token Profile   │  │  Agent Status    │  │    Metrics       │  │     │
│  │  │  (TTL: 24h)      │  │  (TTL: 30m)      │  │  (TTL: 24h~90d)  │  │     │
│  │  └──────────────────┘  └──────────────────┘  └──────────────────┘  │     │
│  └────────────────────────────────┬───────────────────────────────────┘     │
│                                   │                                         │
│         ┌─────────────────────────┼─────────────────────────┐               │
│         ▼                         ▼                         ▼               │
│  ┌─────────────────┐    ┌─────────────────────┐    ┌─────────────────┐      │
│  │   EventBridge   │    │ API Gateway         │    │   CloudWatch    │      │
│  │  ┌───────────┐  │    │   WebSocket         │    │      Logs       │      │
│  │  │ 1min rule │  │    │  ┌─────────────┐    │    │                 │      │
│  │  │ 15min rule│  │    │  │ $connect    │    │    │  STATUS_UPDATE  │      │
│  │  └─────┬─────┘  │    │  │ $disconnect │    │    │     events      │      │
│  └────────┼────────┘    │  └──────┬──────┘    │    └────────┬────────┘      │
│           │             └─────────┼───────────┘             │               │
│           ▼                       ▼                         ▼               │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │                         Lambda Functions                           │     │
│  │  ┌────────────┐  ┌────────────┐  ┌──────────────┐  ┌────────────┐  │     │
│  │  │  Connect   │  │ Disconnect │  │    State     │  │  Metrics   │  │     │
│  │  │  Handler   │  │  Handler   │  │  Transition  │  │ Aggregator │  │     │
│  │  └────────────┘  └────────────┘  └──────────────┘  └────────────┘  │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                   │                                         │
│                           WebSocket Broadcast                               │
│                    (API Gateway Management API)                             │
│                                   │                                         │
│         ┌─────────────────────────┼─────────────────────────┐               │
│         ▼                         ▼                         ▼               │
│  ┌─────────────┐          ┌─────────────┐          ┌─────────────┐          │
│  │   Desktop   │          │     Web     │          │    ESP32    │          │
│  │     App     │          │  Dashboard  │          │  Hardware   │          │
│  └─────────────┘          └─────────────┘          └─────────────┘          │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
</code></pre></div></div>

<p><strong>Key Components:</strong></p>
<ul>
  <li><strong>Amplify</strong>: Next.js app hosting with auto build/deploy</li>
  <li><strong>DynamoDB</strong>: Single table design for tokens, statuses, and metrics</li>
  <li><strong>API Gateway WebSocket</strong>: Real-time bidirectional communication</li>
  <li><strong>Lambda</strong>: Connection management, auto state transition, metrics collection</li>
  <li><strong>EventBridge</strong>: 1min/15min scheduled Lambda triggers</li>
  <li><strong>CloudWatch Logs</strong>: Status event logging, metrics query source</li>
</ul>

<p>Token-based authentication so each user sees only their projects. Tokens auto-register on first use and expire after 24 hours.</p>

<h2 id="phase-5-character-expansion-early-february">Phase 5: Character Expansion (Early February)</h2>

<p>Why support only Claude Code? Let’s support other AI assistants too!</p>

<table>
  <thead>
    <tr>
      <th>Character</th>
      <th>Color</th>
      <th>Target</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>clawd</strong></td>
      <td>Orange</td>
      <td>Claude Code</td>
    </tr>
    <tr>
      <td><strong>kiro</strong></td>
      <td>White</td>
      <td>AWS Kiro</td>
    </tr>
    <tr>
      <td><strong>claw</strong></td>
      <td>Red</td>
      <td>OpenClaw</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/images/2026-02-05/clawd.png" alt="clawd" style="background-color: #1e3a5f; padding: 8px; border-radius: 8px;" /> <img src="/assets/images/2026-02-05/kiro.png" alt="kiro" style="background-color: #2d2d2d; padding: 8px; border-radius: 8px;" /> <img src="/assets/images/2026-02-05/claw.png" alt="claw" style="background-color: #1a1a2e; padding: 8px; border-radius: 8px;" /></p>

<p>Pixel art characters are based on each AI agent’s official character images. Added state-specific expressions — blinking eyes, sunglasses, sleeping eyes, thought bubbles, etc. Rendered as 128x128 PNG images, with characters automatically selected based on hook events.</p>

<h2 id="phase-6-project-unification-february-5">Phase 6: Project Unification (February 5)</h2>

<p>Didn’t like the name “vibe-monitor.” Unified to <strong>VibeMon</strong> for cleaner branding!</p>

<h2 id="phase-7-let-ai-install-itself">Phase 7: Let AI Install Itself</h2>

<p>Wanted to automate the installation process. But then I thought, <strong>why not just have the AI do it?</strong></p>

<p>Created an installation guide as a markdown file, and just tell the AI:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Read https://vibemon.io/setup.md and follow the instructions to join VibeMon
</code></pre></div></div>

<p>Claude Code reads setup.md, configures hooks, generates tokens, and tests everything. Meta.</p>

<h2 id="vibemon-by-the-numbers">VibeMon by the Numbers</h2>

<table>
  <thead>
    <tr>
      <th>Item</th>
      <th>vibemon-app</th>
      <th>vibemon</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Start date</td>
      <td>2026-01-24</td>
      <td>2026-02-01</td>
    </tr>
    <tr>
      <td>Total commits</td>
      <td>518</td>
      <td>101</td>
    </tr>
    <tr>
      <td>Development period</td>
      <td>12 days</td>
      <td>4 days</td>
    </tr>
  </tbody>
</table>

<p><strong>619 commits</strong>, <strong>16 days of development</strong>. Most of it coded with AI.</p>

<p><img src="/assets/images/2026-02-05/bruce_ultrathink.jpg" alt="Developing with AI" /></p>

<h2 id="special-features">Special Features</h2>

<h3 id="1-state-based-animations">1. State-based Animations</h3>

<table>
  <thead>
    <tr>
      <th>State</th>
      <th>Background</th>
      <th>Eye Type</th>
      <th>Text</th>
      <th>Trigger</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>start</td>
      <td>Cyan</td>
      <td>■ ■ + ✦</td>
      <td>Hello!</td>
      <td>Session starts</td>
    </tr>
    <tr>
      <td>idle</td>
      <td>Green</td>
      <td>■ ■ (blinking)</td>
      <td>Ready</td>
      <td>Waiting for input</td>
    </tr>
    <tr>
      <td>thinking</td>
      <td>Purple</td>
      <td>▀ ▀ + 💭</td>
      <td>Thinking</td>
      <td>Prompt submitted</td>
    </tr>
    <tr>
      <td>planning</td>
      <td>Teal</td>
      <td>▀ ▀ + 💭</td>
      <td>Planning</td>
      <td>Plan mode active</td>
    </tr>
    <tr>
      <td>working</td>
      <td>Blue</td>
      <td>🕶️ (sunglasses)</td>
      <td>(tool-based)</td>
      <td>Tool executing</td>
    </tr>
    <tr>
      <td>packing</td>
      <td>Gray</td>
      <td>▀ ▀ + 💭</td>
      <td>Packing</td>
      <td>Context compacting</td>
    </tr>
    <tr>
      <td>notification</td>
      <td>Yellow</td>
      <td>● ● + ?</td>
      <td>Input?</td>
      <td>User input needed</td>
    </tr>
    <tr>
      <td>done</td>
      <td>Green</td>
      <td>&gt; &lt;</td>
      <td>Done!</td>
      <td>Tool completed</td>
    </tr>
    <tr>
      <td>sleep</td>
      <td>Navy</td>
      <td>─ ─ + Z</td>
      <td>Zzz…</td>
      <td>5min inactive</td>
    </tr>
  </tbody>
</table>

<h3 id="2-working-state-text">2. Working State Text</h3>

<p>Different text displayed based on tool:</p>

<table>
  <thead>
    <tr>
      <th>Tool</th>
      <th>Display Text</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Bash</td>
      <td>Running, Executing, Processing</td>
    </tr>
    <tr>
      <td>Read</td>
      <td>Reading, Scanning, Checking</td>
    </tr>
    <tr>
      <td>Edit</td>
      <td>Editing, Modifying, Fixing</td>
    </tr>
    <tr>
      <td>Grep</td>
      <td>Searching, Finding, Looking</td>
    </tr>
  </tbody>
</table>

<h3 id="3-memory-bar">3. Memory Bar</h3>

<p>Context window usage shown as gradient:</p>
<ul>
  <li>0-74%: 💚 Green</li>
  <li>75-89%: 💛 Yellow (warning)</li>
  <li>90-100%: ❤️ Red (danger!)</li>
</ul>

<h3 id="4-click-to-focus-terminal">4. Click to Focus Terminal</h3>

<p>On macOS, clicking the VibeMon window automatically switches to that project’s iTerm2/Ghostty tab! Implemented with AppleScript.</p>

<h2 id="lessons-learned">Lessons Learned</h2>

<ol>
  <li>
    <p><strong>Single Table Design</strong> is truly efficient. Could handle diverse queries with PK/SK combinations in DynamoDB.</p>
  </li>
  <li>
    <p><strong>WebSocket + API Gateway</strong> combination. Implementing real-time communication serverlessly was more complex than expected.</p>
  </li>
  <li>
    <p><strong>Pixel art is harder than expected</strong>. Conveying emotion on a small canvas isn’t easy.</p>
  </li>
  <li>
    <p><strong>Pair programming with AI</strong>. Built VibeMon while developing with Claude Code, then used VibeMon to monitor Claude Code. Meta.</p>
  </li>
</ol>

<h2 id="conclusion">Conclusion</h2>

<p>VibeMon started from a simple question: “What should I display on this small LCD screen?” Now it’s grown into a project supporting ESP32 hardware, Desktop app, and web dashboard.</p>

<p>Next goals:</p>
<ul>
  <li>Support more AI tools (Cursor, Windsurf, etc.)</li>
  <li>Team features (multiple users’ status on one screen)</li>
  <li>ESP32 custom case design (3D printing)</li>
</ul>

<p>Having a cute pixel character accompany me while coding makes it feel less lonely.</p>

<h2 id="thank-you">Thank You</h2>

<p>This project was built with AI. Pair programming with Claude Code, monitoring Claude Code with VibeMon, and feeding that experience back into VibeMon — it was a fun recursive loop.</p>

<p>Thanks for reading this long post. Please try VibeMon, and feedback or contributions are always welcome!</p>

<hr />

<p><strong>Links:</strong></p>
<ul>
  <li>Dashboard: <a href="https://vibemon.io">vibemon.io</a></li>
  <li>Desktop App: <a href="https://www.npmjs.com/package/vibemon">npm - vibemon</a></li>
  <li>GitHub: <a href="https://github.com/nalbam/vibemon">nalbam/vibemon</a>, <a href="https://github.com/nalbam/vibemon-app">nalbam/vibemon-app</a></li>
</ul>]]></content><author><name></name></author><category term="vibemon" /><category term="esp32" /><category term="arduino" /><category term="claude" /><category term="ai" /><category term="websocket" /><category term="electron" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">VibeMon 제작기: AI 코딩 어시스턴트의 상태를 귀여운 픽셀 아트로 보여주기</title><link href="https://nalbam.github.io//2026/02/05/vibemon-ko.html" rel="alternate" type="text/html" title="VibeMon 제작기: AI 코딩 어시스턴트의 상태를 귀여운 픽셀 아트로 보여주기" /><published>2026-02-05T00:00:00+00:00</published><updated>2026-02-05T00:00:00+00:00</updated><id>https://nalbam.github.io//2026/02/05/vibemon-ko</id><content type="html" xml:base="https://nalbam.github.io//2026/02/05/vibemon-ko.html"><![CDATA[<p><img src="/assets/images/2026-02-05/demo.gif" alt="VibeMon Demo" /></p>

<h2 id="시작은-작은-lcd-화면에서">시작은 작은 LCD 화면에서</h2>

<p>2026년 1월, 책상 위에 놓을 작은 LCD 디스플레이가 갖고 싶었다. ESP32와 172×320 해상도의 작은 화면. 여기에 뭘 띄우면 좋을까?</p>

<p>Claude Code로 코딩하다 문득 떠올랐다. “AI가 지금 뭘 하고 있는지 이 화면에 보여주면 어떨까?”</p>

<p>터미널 창을 계속 쳐다보고 있을 수도 없고, 언제 작업이 끝나는지 알 수도 없었다. 그래서 시작했다 — AI 코딩 어시스턴트가 지금 무엇을 하는지 한눈에 보여주는 모니터를 만들기로.</p>

<h2 id="phase-1-esp32용-프로토타입-1월-24일">Phase 1: ESP32용 프로토타입 (1월 24일)</h2>

<p>ESP32 기기가 배송 오기 전에 개발을 시작했다. 처음엔 “Claude Code Status Display”라는 이름으로 시작했다. 이 코드는 현재 <a href="https://github.com/nalbam/vibemon-app">vibemon-app</a> 레포에서 확인할 수 있다. 목표는 단순했다:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Claude가 thinking 상태면 → 화면에 "생각 중..." 표시
Claude가 working 상태면 → 화면에 "작업 중..." 표시
</code></pre></div></div>

<p>하지만 그냥 텍스트만 보여주면 재미없잖아? 그래서 <strong>픽셀 아트 캐릭터</strong>를 넣었다.</p>

<p>캐릭터 눈 모양부터 시작해서 상태별 표정을 하나씩 만들어갔다:</p>
<ul>
  <li><strong>idle</strong> 상태: ■ ■ (기본 눈)</li>
  <li><strong>thinking</strong> 상태: ▀ ▀ + 💭 (생각 버블)</li>
  <li><strong>working</strong> 상태: 🕶️ (선글라스 - 매트릭스 스타일!)</li>
  <li><strong>notification</strong> 상태: ● ● + ? (물음표)</li>
  <li><strong>done</strong> 상태: &gt; &lt; (뿌듯한 눈)</li>
</ul>

<h2 id="phase-2-simulator와-desktop-app-1월-24일--2월-1일">Phase 2: Simulator와 Desktop App (1월 24일 ~ 2월 1일)</h2>

<p>ESP32 기기가 도착하기 전에 테스트할 방법이 필요했다. 그래서 <strong>웹 시뮬레이터</strong>를 먼저 만들었다. 브라우저에서 ESP32 화면을 미리 볼 수 있게.</p>

<p>그리고 기다리는 동안 <strong>Desktop App</strong>도 만들었다. Electron으로 만든 이 앱은 나중에 ESP32 없이도 쓸 수 있는 대안이 됐다.</p>

<p><strong>주요 기능들이 빠르게 추가됐다:</strong></p>

<table>
  <thead>
    <tr>
      <th>기능</th>
      <th>설명</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Always on Top</td>
      <td>다른 창 위에 항상 표시</td>
    </tr>
    <tr>
      <td>System Tray</td>
      <td>메뉴바에서 빠르게 제어</td>
    </tr>
    <tr>
      <td>Frameless Window</td>
      <td>깔끔한 플로팅 디자인</td>
    </tr>
    <tr>
      <td>Floating Animation</td>
      <td>둥실둥실 떠다니는 효과</td>
    </tr>
  </tbody>
</table>

<p>그리고 <strong>Claude Code hooks</strong>와 연동! Claude Code가 상태를 바꿀 때마다 자동으로 앱에 전달되도록 했다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Claude Code hooks로 상태 전송</span>
curl <span class="nt">-X</span> POST http://127.0.0.1:19280/status <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{"state":"working","tool":"Bash","project":"my-project"}'</span>
</code></pre></div></div>

<h2 id="연동-방식의-진화">연동 방식의 진화</h2>

<p>Claude Code와 VibeMon을 연결하는 방식도 계속 발전했다.</p>

<h3 id="1단계-로컬-http">1단계: 로컬 HTTP</h3>

<p>처음엔 단순했다. Claude Code hooks에서 Desktop App으로 직접 전송.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> POST http://127.0.0.1:19280/status <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{"state":"working"}'</span>
</code></pre></div></div>

<h3 id="2단계-usb-시리얼">2단계: USB 시리얼</h3>

<p>ESP32가 도착했다! <a href="https://aliexpress.com/item/1005008465501661.html"><strong>ESP32-C6-LCD-1.47</strong></a> 보드 — 172×320 해상도의 작은 LCD가 달린 귀여운 기기.</p>

<p><img src="/assets/images/2026-02-05/esp32_and_desktop.jpg" alt="ESP32와 Desktop App" /></p>

<p>아두이노는 처음 써봤지만, AI 덕분에 쉽게 설정했다. Claude Code에게 물어보니 순식간에 가이드를 만들어줬다.</p>

<p><strong>아두이노 IDE 설정:</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. Arduino IDE 설치
2. ESP32 보드 매니저 추가 (Preferences → Additional Board URLs)
   https://espressif.github.io/arduino-esp32/package_esp32_index.json
3. 보드 선택: ESP32C6 Dev Module
4. 파티션 스킴: Huge APP (3MB No OTA/1MB SPIFFS)  ← SSL 지원 필수!
</code></pre></div></div>

<p><strong>필요한 라이브러리:</strong></p>

<table>
  <thead>
    <tr>
      <th>라이브러리</th>
      <th>용도</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>LovyanGFX</td>
      <td>TFT 디스플레이 제어 (ESP32-C6 호환)</td>
    </tr>
    <tr>
      <td>ArduinoJson</td>
      <td>JSON 파싱</td>
    </tr>
    <tr>
      <td>WebSockets</td>
      <td>WebSocket 클라이언트</td>
    </tr>
  </tbody>
</table>

<p>빌드하고 업로드! USB 케이블로 연결하면 시리얼 통신으로 상태가 전송된다.</p>

<h3 id="3단계-내부-네트워크-http">3단계: 내부 네트워크 HTTP</h3>

<p>어차피 ESP32도 WiFi 접속하고 웹서버가 돌아가잖아? USB 케이블 없이 같은 네트워크에서 HTTP로 전송하면 된다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> POST http://192.168.1.xxx/status <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{"state":"working"}'</span>
</code></pre></div></div>

<h3 id="4단계-클라우드-websocket">4단계: 클라우드 WebSocket</h3>

<p>OpenClaw를 지원하면서 욕심이 생겼다. 집에서도, 회사에서도, 모든 AI 에이전트의 상태를 <strong>한 화면</strong>에서 보고 싶었다.</p>

<p>그래서 <strong>vibemon.io</strong> 도메인을 등록하고, 웹 대시보드와 WebSocket 서버를 구축했다. 이제 어디서든 실시간으로 모니터링할 수 있다.</p>

<p>OpenClaw는 <strong>플러그인 방식</strong>으로 VibeMon을 설치할 수 있게 했다. Claude Code의 hooks와 달리, OpenClaw는 플러그인 시스템을 통해 확장 기능을 추가할 수 있었다. 결국 OpenClaw에게도 스스로 설치하게 만들었다 — AI가 플러그인을 설치하고 설정하는 메타한 상황.</p>

<h3 id="숨겨진-난관-model과-memory">숨겨진 난관: model과 memory</h3>

<p>Claude Code hooks에서는 <code class="language-plaintext highlighter-rouge">state</code>, <code class="language-plaintext highlighter-rouge">tool</code>, <code class="language-plaintext highlighter-rouge">project</code> 정보는 알 수 있지만, <strong>model</strong>과 <strong>memory</strong> 정보는 직접 제공되지 않는다.</p>

<p>그런데 이 정보들이 화면에 보이면 훨씬 유용하지 않을까? 어떤 모델을 쓰고 있는지, 컨텍스트 윈도우를 얼마나 사용했는지.</p>

<p>해결책은 <strong>statusline</strong>이었다. Claude Code의 statusline에는 model과 memory 정보가 표시된다. 그래서 <code class="language-plaintext highlighter-rouge">statusline.py</code>를 만들어서:</p>

<ol>
  <li>statusline에서 model, memory 정보를 파싱</li>
  <li>프로젝트별로 캐시 파일에 저장 (<code class="language-plaintext highlighter-rouge">~/.claude/vibemon/{project}.json</code>)</li>
  <li>hook이 실행될 때 캐시에서 데이터를 읽어서 함께 전송</li>
</ol>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># statusline.py - model/memory 정보를 캐시에 저장
</span><span class="n">cache</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"model"</span><span class="p">:</span> <span class="s">"Opus 4.5"</span><span class="p">,</span>
    <span class="s">"memory"</span><span class="p">:</span> <span class="mi">45</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># hook - 캐시에서 읽어서 전송
</span><span class="n">status</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"state"</span><span class="p">:</span> <span class="s">"working"</span><span class="p">,</span>
    <span class="s">"tool"</span><span class="p">:</span> <span class="s">"Bash"</span><span class="p">,</span>
    <span class="s">"project"</span><span class="p">:</span> <span class="s">"vibemon"</span><span class="p">,</span>
    <span class="s">"model"</span><span class="p">:</span> <span class="n">cache</span><span class="p">[</span><span class="s">"model"</span><span class="p">],</span>    <span class="c1"># statusline에서 가져온 값
</span>    <span class="s">"memory"</span><span class="p">:</span> <span class="n">cache</span><span class="p">[</span><span class="s">"memory"</span><span class="p">]</span>   <span class="c1"># statusline에서 가져온 값
</span><span class="p">}</span>
</code></pre></div></div>

<p>이렇게 두 개의 스크립트가 협력해서 완전한 상태 정보를 전송할 수 있게 됐다.</p>

<p>반면 <strong>Kiro</strong>는 model과 memory 정보를 알아낼 방법이 전혀 없었다. statusline 같은 기능도 없고, hook에서 제공하는 정보도 제한적이다. 그래서 Kiro 캐릭터는 state, tool, project만 표시된다. 아쉽지만 어쩔 수 없다.</p>

<h2 id="phase-3-멀티-프로젝트-지원-1월-말">Phase 3: 멀티 프로젝트 지원 (1월 말)</h2>

<p>여러 프로젝트를 동시에 작업하면 어떡하지? → <strong>멀티 윈도우 모드</strong> 탄생!</p>

<p><img src="/assets/images/2026-02-05/desktop_multi_windows.png" alt="멀티 윈도우 모드" /></p>

<p>프로젝트마다 별도의 창이 뜨고, 활성 상태인 프로젝트는 오른쪽에, 비활성은 왼쪽에 자동 정렬된다. 최대 5개 창까지 지원.</p>

<h2 id="phase-4-웹-대시보드-구축-2월-1일">Phase 4: 웹 대시보드 구축 (2월 1일)</h2>

<p>로컬에서만 쓰기엔 아쉬웠다. 집, 회사 등 여러 곳의 AI 에이전트 상태를 한 곳에서 보고 싶었다. 그래서 <a href="https://github.com/nalbam/vibemon">vibemon</a> 웹 대시보드 프로젝트 시작!</p>

<h3 id="기술-스택-선택">기술 스택 선택</h3>

<ul>
  <li><strong>Frontend</strong>: Next.js 16 + React 19 + TypeScript</li>
  <li><strong>Database</strong>: AWS DynamoDB (Single Table Design)</li>
  <li><strong>Real-time</strong>: WebSocket</li>
  <li><strong>Infrastructure</strong>: Terraform (API Gateway, Lambda)</li>
  <li><strong>Hosting</strong>: AWS Amplify</li>
</ul>

<h3 id="아키텍처">아키텍처</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────────────────────────────────────────────┐
│                           VibeMon Architecture                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐                      │
│  │ Claude Code │    │    Kiro     │    │  OpenClaw   │                      │
│  │   (clawd)   │    │   (kiro)    │    │   (claw)    │                      │
│  └──────┬──────┘    └──────┬──────┘    └──────┬──────┘                      │
│         │                  │                  │                             │
│         │   Hooks          │   Hooks          │                             │
│         └──────────────────┼──────────────────┘                             │
│                            ▼                                                │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                      AWS Amplify (Hosting)                          │    │
│  │  ┌───────────────────────────────────────────────────────────────┐  │    │
│  │  │              Next.js 16 + React 19 + TypeScript               │  │    │
│  │  │  ┌─────────────────────┐    ┌─────────────────────────────┐   │  │    │
│  │  │  │   POST /api/status  │    │   GET /api/statuses,metrics │   │  │    │
│  │  │  │  (Bearer Token Auth)│    │                             │   │  │    │
│  │  │  └──────────┬──────────┘    └──────────────┬──────────────┘   │  │    │
│  │  └─────────────┼──────────────────────────────┼──────────────────┘  │    │
│  └────────────────┼──────────────────────────────┼─────────────────────┘    │
│                   │                              │                          │
│                   ▼                              ▼                          │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │                    DynamoDB (Single Table Design)                  │     │
│  │  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐  │     │
│  │  │  Token Profile   │  │  Agent Status    │  │    Metrics       │  │     │
│  │  │  (TTL: 24h)      │  │  (TTL: 30m)      │  │  (TTL: 24h~90d)  │  │     │
│  │  └──────────────────┘  └──────────────────┘  └──────────────────┘  │     │
│  └────────────────────────────────┬───────────────────────────────────┘     │
│                                   │                                         │
│         ┌─────────────────────────┼─────────────────────────┐               │
│         ▼                         ▼                         ▼               │
│  ┌─────────────────┐    ┌─────────────────────┐    ┌─────────────────┐      │
│  │   EventBridge   │    │ API Gateway         │    │   CloudWatch    │      │
│  │  ┌───────────┐  │    │   WebSocket         │    │      Logs       │      │
│  │  │ 1min rule │  │    │  ┌─────────────┐    │    │                 │      │
│  │  │ 15min rule│  │    │  │ $connect    │    │    │  STATUS_UPDATE  │      │
│  │  └─────┬─────┘  │    │  │ $disconnect │    │    │     events      │      │
│  └────────┼────────┘    │  └──────┬──────┘    │    └────────┬────────┘      │
│           │             └─────────┼───────────┘             │               │
│           ▼                       ▼                         ▼               │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │                         Lambda Functions                           │     │
│  │  ┌────────────┐  ┌────────────┐  ┌──────────────┐  ┌────────────┐  │     │
│  │  │  Connect   │  │ Disconnect │  │    State     │  │  Metrics   │  │     │
│  │  │  Handler   │  │  Handler   │  │  Transition  │  │ Aggregator │  │     │
│  │  └────────────┘  └────────────┘  └──────────────┘  └────────────┘  │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                   │                                         │
│                           WebSocket Broadcast                               │
│                    (API Gateway Management API)                             │
│                                   │                                         │
│         ┌─────────────────────────┼─────────────────────────┐               │
│         ▼                         ▼                         ▼               │
│  ┌─────────────┐          ┌─────────────┐          ┌─────────────┐          │
│  │   Desktop   │          │     Web     │          │    ESP32    │          │
│  │     App     │          │  Dashboard  │          │  Hardware   │          │
│  └─────────────┘          └─────────────┘          └─────────────┘          │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
</code></pre></div></div>

<p><strong>주요 구성 요소:</strong></p>
<ul>
  <li><strong>Amplify</strong>: Next.js 앱 호스팅, 자동 빌드/배포</li>
  <li><strong>DynamoDB</strong>: 단일 테이블 설계로 토큰, 상태, 메트릭 저장</li>
  <li><strong>API Gateway WebSocket</strong>: 실시간 양방향 통신</li>
  <li><strong>Lambda</strong>: 연결 관리, 상태 자동 전환, 메트릭 수집</li>
  <li><strong>EventBridge</strong>: 1분/15분 주기 Lambda 트리거</li>
  <li><strong>CloudWatch Logs</strong>: 상태 이벤트 로깅, 메트릭 쿼리 소스</li>
</ul>

<p>토큰 기반 인증으로 각자의 프로젝트만 볼 수 있게 했다. 토큰은 처음 사용할 때 자동 등록되고, 24시간 후 만료된다.</p>

<h2 id="phase-5-캐릭터-확장-2월-초">Phase 5: 캐릭터 확장 (2월 초)</h2>

<p>Claude Code만 지원하면 뭐하나. 다른 AI 어시스턴트도 지원하자!</p>

<table>
  <thead>
    <tr>
      <th>캐릭터</th>
      <th>색상</th>
      <th>대상</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>clawd</strong></td>
      <td>주황색</td>
      <td>Claude Code</td>
    </tr>
    <tr>
      <td><strong>kiro</strong></td>
      <td>흰색</td>
      <td>AWS Kiro</td>
    </tr>
    <tr>
      <td><strong>claw</strong></td>
      <td>빨간색</td>
      <td>OpenClaw</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/images/2026-02-05/clawd.png" alt="clawd" style="background-color: #1e3a5f; padding: 8px; border-radius: 8px;" /> <img src="/assets/images/2026-02-05/kiro.png" alt="kiro" style="background-color: #2d2d2d; padding: 8px; border-radius: 8px;" /> <img src="/assets/images/2026-02-05/claw.png" alt="claw" style="background-color: #1a1a2e; padding: 8px; border-radius: 8px;" /></p>

<p>픽셀 아트 캐릭터는 각 AI 에이전트의 공식 캐릭터 이미지를 기반으로 했다. 거기에 상태별 표정을 추가했다 — 깜빡이는 눈, 선글라스, 잠자는 눈, 생각 버블 등. 128x128 PNG 이미지로 렌더링되고, hook 이벤트에 따라 자동으로 캐릭터가 선택된다.</p>

<h2 id="phase-6-프로젝트-통합-2월-5일">Phase 6: 프로젝트 통합 (2월 5일)</h2>

<p>“vibe-monitor”라는 이름이 마음에 안 들었다. 더 깔끔하게 <strong>VibeMon</strong>으로 통일!</p>

<h2 id="phase-7-ai가-직접-설치하게-하기">Phase 7: AI가 직접 설치하게 하기</h2>

<p>설치 과정을 자동화하고 싶었다. 그런데 생각해보니, <strong>AI한테 직접 시키면 되잖아?</strong></p>

<p>설치 가이드를 markdown 파일로 만들고, AI에게 이렇게 말하면 된다:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Read https://vibemon.io/setup.md and follow the instructions to join VibeMon
</code></pre></div></div>

<p>Claude Code가 직접 setup.md를 읽고, hooks 설정하고, 토큰 생성하고, 테스트까지 해준다. 메타하다.</p>

<h2 id="숫자로-보는-vibemon">숫자로 보는 VibeMon</h2>

<table>
  <thead>
    <tr>
      <th>항목</th>
      <th>vibemon-app</th>
      <th>vibemon</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>시작일</td>
      <td>2026-01-24</td>
      <td>2026-02-01</td>
    </tr>
    <tr>
      <td>총 커밋</td>
      <td>518</td>
      <td>101</td>
    </tr>
    <tr>
      <td>개발 기간</td>
      <td>12일</td>
      <td>4일</td>
    </tr>
  </tbody>
</table>

<p><strong>619개의 커밋</strong>, <strong>16일간의 개발</strong>. 대부분 AI와 함께 짰다.</p>

<p><img src="/assets/images/2026-02-05/bruce_ultrathink.jpg" alt="AI와 함께 개발 중" /></p>

<h2 id="특별한-기능들">특별한 기능들</h2>

<h3 id="1-상태별-애니메이션">1. 상태별 애니메이션</h3>

<table>
  <thead>
    <tr>
      <th>상태</th>
      <th>배경색</th>
      <th>눈 모양</th>
      <th>텍스트</th>
      <th>트리거</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>start</td>
      <td>Cyan</td>
      <td>■ ■ + ✦</td>
      <td>Hello!</td>
      <td>세션 시작</td>
    </tr>
    <tr>
      <td>idle</td>
      <td>Green</td>
      <td>■ ■ (깜빡임)</td>
      <td>Ready</td>
      <td>입력 대기</td>
    </tr>
    <tr>
      <td>thinking</td>
      <td>Purple</td>
      <td>▀ ▀ + 💭</td>
      <td>Thinking</td>
      <td>프롬프트 제출</td>
    </tr>
    <tr>
      <td>planning</td>
      <td>Teal</td>
      <td>▀ ▀ + 💭</td>
      <td>Planning</td>
      <td>Plan 모드 활성화</td>
    </tr>
    <tr>
      <td>working</td>
      <td>Blue</td>
      <td>🕶️ (선글라스)</td>
      <td>(도구별)</td>
      <td>도구 실행 중</td>
    </tr>
    <tr>
      <td>packing</td>
      <td>Gray</td>
      <td>▀ ▀ + 💭</td>
      <td>Packing</td>
      <td>컨텍스트 압축</td>
    </tr>
    <tr>
      <td>notification</td>
      <td>Yellow</td>
      <td>● ● + ?</td>
      <td>Input?</td>
      <td>사용자 입력 필요</td>
    </tr>
    <tr>
      <td>done</td>
      <td>Green</td>
      <td>&gt; &lt;</td>
      <td>Done!</td>
      <td>도구 완료</td>
    </tr>
    <tr>
      <td>sleep</td>
      <td>Navy</td>
      <td>─ ─ + Z</td>
      <td>Zzz…</td>
      <td>5분 비활성</td>
    </tr>
  </tbody>
</table>

<h3 id="2-working-상태-텍스트">2. Working 상태 텍스트</h3>

<p>도구에 따라 다른 텍스트가 표시된다:</p>

<table>
  <thead>
    <tr>
      <th>Tool</th>
      <th>표시 텍스트</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Bash</td>
      <td>Running, Executing, Processing</td>
    </tr>
    <tr>
      <td>Read</td>
      <td>Reading, Scanning, Checking</td>
    </tr>
    <tr>
      <td>Edit</td>
      <td>Editing, Modifying, Fixing</td>
    </tr>
    <tr>
      <td>Grep</td>
      <td>Searching, Finding, Looking</td>
    </tr>
  </tbody>
</table>

<h3 id="3-메모리-바">3. 메모리 바</h3>

<p>컨텍스트 윈도우 사용률을 그라데이션으로 표시:</p>
<ul>
  <li>0-74%: 💚 초록</li>
  <li>75-89%: 💛 노랑 (경고)</li>
  <li>90-100%: ❤️ 빨강 (위험!)</li>
</ul>

<h3 id="4-클릭해서-터미널-포커스">4. 클릭해서 터미널 포커스</h3>

<p>macOS에서 VibeMon 창을 클릭하면 해당 프로젝트의 iTerm2/Ghostty 탭으로 자동 전환! AppleScript로 구현했다.</p>

<h2 id="배운-것들">배운 것들</h2>

<ol>
  <li>
    <p><strong>Single Table Design</strong>이 정말 효율적이다. DynamoDB에서 PK/SK 조합으로 다양한 쿼리를 처리할 수 있었다.</p>
  </li>
  <li>
    <p><strong>WebSocket + API Gateway</strong>의 조합. 서버리스로 실시간 통신을 구현하는 게 생각보다 복잡했다.</p>
  </li>
  <li>
    <p><strong>픽셀 아트는 생각보다 어렵다</strong>. 작은 캔버스에 감정을 담는 게 쉽지 않았다.</p>
  </li>
  <li>
    <p><strong>AI와 페어 프로그래밍</strong>. Claude Code와 함께 개발하면서 VibeMon을 만들고, 그 VibeMon으로 Claude Code를 모니터링했다. 메타하다.</p>
  </li>
</ol>

<h2 id="마무리">마무리</h2>

<p>VibeMon은 “이 작은 LCD 화면에 뭘 띄울까?”라는 단순한 질문에서 시작했다. 지금은 ESP32 하드웨어, Desktop 앱, 웹 대시보드까지 지원하는 프로젝트로 성장했다.</p>

<p>다음 목표는:</p>
<ul>
  <li>더 많은 AI 도구 지원 (Cursor, Windsurf 등)</li>
  <li>팀 기능 (여러 사용자의 상태를 한 화면에)</li>
  <li>ESP32 전용 케이스 디자인 (3D 프린팅)</li>
</ul>

<p>귀여운 픽셀 캐릭터가 코딩하는 동안 함께해주니, 혼자 코딩하는 것 같지 않다.</p>

<h2 id="감사합니다">감사합니다</h2>

<p>이 프로젝트는 AI와 함께 만들었습니다. Claude Code와 페어 프로그래밍을 하면서, VibeMon으로 Claude Code를 모니터링하고, 그 과정을 다시 VibeMon에 반영하는 순환 구조가 재미있었습니다.</p>

<p>긴 글 읽어주셔서 감사합니다. VibeMon을 사용해보시고, 피드백이나 기여는 언제든 환영합니다!</p>

<hr />

<p><strong>Links:</strong></p>
<ul>
  <li>Dashboard: <a href="https://vibemon.io">vibemon.io</a></li>
  <li>Desktop App: <a href="https://www.npmjs.com/package/vibemon">npm - vibemon</a></li>
  <li>GitHub: <a href="https://github.com/nalbam/vibemon">nalbam/vibemon</a>, <a href="https://github.com/nalbam/vibemon-app">nalbam/vibemon-app</a></li>
</ul>]]></content><author><name></name></author><category term="vibemon" /><category term="esp32" /><category term="arduino" /><category term="claude" /><category term="ai" /><category term="websocket" /><category term="electron" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">AWS re:Invent 2022 에서 사용된 딥레이서 타이머</title><link href="https://nalbam.github.io//2022/12/12/deepracer-timer-2022-ko.html" rel="alternate" type="text/html" title="AWS re:Invent 2022 에서 사용된 딥레이서 타이머" /><published>2022-12-12T00:00:00+00:00</published><updated>2022-12-12T00:00:00+00:00</updated><id>https://nalbam.github.io//2022/12/12/deepracer-timer-2022-ko</id><content type="html" xml:base="https://nalbam.github.io//2022/12/12/deepracer-timer-2022-ko.html"><![CDATA[<p>2019년 압력센서 기반의 딥레이서 타이머를 만들었었습니다.</p>

<p>해당 내용은 블로그에 정리해 두었습니다. <a href="https://nalbam.github.io/2019/11/07/deepracer-timer-ko.html">딥레이서 타이머 및 센서 제작기</a></p>

<p>그리고 AWS re:Invent 2022 의 딥레이서 공식 경기에서 이 타이머가 사용되었습니다.</p>

<p>아래 사진은 2019년에 제가 만든 타이머와 2022년에 AWS 가 만든 타이머를 비교한 사진입니다.</p>

<p><img src="/assets/images/2022-12-12/timer-2022-01.png" alt="Pressure Sensor" /></p>

<p><img src="/assets/images/2022-12-12/timer-2022-02.png" alt="Raspberry Pi" /></p>

<p><img src="/assets/images/2022-12-12/timer-2022-03.png" alt="Timer" /></p>]]></content><author><name></name></author><category term="deepracer" /><category term="timer" /><category term="sensor" /><summary type="html"><![CDATA[2019년 압력센서 기반의 딥레이서 타이머를 만들었었습니다.]]></summary></entry><entry><title type="html">AWS코리아 10주년</title><link href="https://nalbam.github.io//2022/08/30/awskorea-10th.html" rel="alternate" type="text/html" title="AWS코리아 10주년" /><published>2022-08-30T00:00:00+00:00</published><updated>2022-08-30T00:00:00+00:00</updated><id>https://nalbam.github.io//2022/08/30/awskorea-10th</id><content type="html" xml:base="https://nalbam.github.io//2022/08/30/awskorea-10th.html"><![CDATA[<p>2022년 올해로 AWS Korea 가 10주년 이라고 합니다.
AWS Korea 10주년을 축하하며, 제가 AWS 와 함께했던 기억을 더듬어 보려고 합니다.</p>

<h2 id="2018년-awskrug-밋업">2018년 AWSKRUG 밋업</h2>

<p>2018년 까지는 AWSKRUG 아키텍처 소모임에서 주로 듣는 입장이었습니다. 2018년 <code class="language-plaintext highlighter-rouge">Java</code> 개발자 에서 <code class="language-plaintext highlighter-rouge">DevOps</code> 영역으로 전직하였고, <code class="language-plaintext highlighter-rouge">AWS</code>, <code class="language-plaintext highlighter-rouge">Terraform</code>, <code class="language-plaintext highlighter-rouge">Serverless</code>, <code class="language-plaintext highlighter-rouge">Kubernetes</code> 등을 본격적으로 다루었습니다. 그리고 해왔던 것들을 소개 할 수 있는 시간을 갖게 되었습니다.</p>

<h2 id="2018년-6월-serverless-with-terraform---awskrug-서버리스-소모임">2018년 6월 <a href="https://www.meetup.com/awskrug/events/251057806/">Serverless with Terraform - AWSKRUG 서버리스 소모임</a></h2>

<p>저의 첫 소모임 밋업 발표였습니다. <code class="language-plaintext highlighter-rouge">Terraform</code> 접하고 AWS 리소스는 Terraform 으로 관리하는게 제일 좋겠다는 생각을 가지게 되었고, AWS <code class="language-plaintext highlighter-rouge">Serverless</code> 를 Terraform 으로 배포하고 운영하는 방법이란 주제로 발표를 했습니다.</p>

<h2 id="2018년-6월-eks-with-terraform---awskrug-컨테이너-소모임">2018년 6월 <a href="https://www.meetup.com/ko-KR/awskrug/events/251467130/">EKS with Terraform - AWSKRUG 컨테이너 소모임</a></h2>

<p><code class="language-plaintext highlighter-rouge">Kubernetes</code> 는 이전부터 계속 다루고 있었지만, AWS 환경에서는 직접 구성하거나, <code class="language-plaintext highlighter-rouge">KOPS</code> 로 구성할수 밖에 없었습니다. 그때 AWS 는 Kubernetes 의 매니지드 서비스인 Amazon EKS 를 발표하였고, 동시에 <code class="language-plaintext highlighter-rouge">Terraform</code> 으로 구성하는 방법을 소개 하였습니다. 빠르게 Terraform 으로 EKS 클러스터를 구성해보고 바로 밋업에서 발표 했습니다.</p>

<h2 id="2018년-6월-kubernetes-with-kops---openinfra-days-korea-2018">2018년 6월 <a href="https://github.com/awskrug/handson-labs-2018/blob/master/OpenInfraDays/hands-on.md">Kubernetes with KOPS - Openinfra Days Korea 2018</a></h2>

<p>Amazon EKS 가 나왔지만, 서울 리전에서는 아직 사용 할 수 없었습니다. 그래서 Kubernetes 클러스터를 생성하기 위해서는 아직은 KOPS 를 사용 해야 했고, <code class="language-plaintext highlighter-rouge">Openinfra Days Korea 2018</code> 에서 발표와 핸즈온을 진행 했습니다.</p>

<p><img src="/assets/images/2022-08-30/Openinfra-Days-Korea-2018.png" alt="Openinfra Days Korea 2018" /></p>

<h2 id="2018년-7월-kubernetes-with-kops---awskrug-hands-on-lab-2018---container-3">2018년 7월 <a href="https://www.meetup.com/awskrug/events/251854018/">Kubernetes with KOPS - AWSKRUG Hands-on Lab 2018 - Container #3</a></h2>

<p>2018년 6월 2일부터 10월 20일까지 9회에 걸쳐 <code class="language-plaintext highlighter-rouge">AWSKRUG Hands-on Lab 2018</code> 을 진행 했습니다. 저는 컨테이너 3번째 KOPS 로 Kubernetes 클러스터를 생성하고, 간단한 Addons 와 HPA, CA 테스트 까지를 Hands-on 으로 진행 했습니다. 이제 언제 이런 핸즈온 장기레이스를 다시 진행 할 수 있을까 싶네요.</p>

<p><img src="/assets/images/2022-08-30/AWSKRUG-Hands-on-Lab-2018.png" alt="AWSKRUG Hands-on Lab 2018" /></p>

<h2 id="2018년-11월-kops-cui---aws-dev-day-2018-커뮤니티트랙">2018년 11월 kops-cui - AWS Dev Day 2018 커뮤니티트랙</h2>

<p><code class="language-plaintext highlighter-rouge">AWS Dev Day 2018</code> 커뮤니티트랙 에서 KOPS 로 Kubernetes 클러스터를 생성하고, Helm 으로 패키지를 관리하고, AWS 리소스와 연동 하기 위해 만든 kops-cui 라는 툴을 소개하는 발표도 진행 했습니다.</p>

<div class="youtube-wrapper">
  <iframe src="https://www.youtube.com/embed/3IvZ1NDEFcc" allowfullscreen=""></iframe>
</div>

<h2 id="aws-reinvent-2018">AWS re:Invent 2018</h2>

<p>그리고 2018년 말 AWSKRUG 의 지원으로 미국 라스베가스에서 열리는 <code class="language-plaintext highlighter-rouge">AWS re:Invent 2018</code> 에 참석했습니다.
라스베가스에 가기전 시에틀에서 아마존 본사도 방문하고, 아마존처럼 꾸민 아마존 스피어도 가보고, 물건을 집어서 나가면 캐셔없이도 결제되는 Amazon Go 도 가보고, 스타벅스 1호점도 가보고, 첫 미국여행을 알차게 보냈네요.
그리고 본 행사인 리인벤트에서는 저의 인생을 바꿔줄 <code class="language-plaintext highlighter-rouge">AWS DeepRacer</code> 가 발표 되었습니다. 딥레이서 첫번째 세션을 워크인으로 입장해서 딥레이서 자동차를 받고, 다른 세션은 모두 패스하고 딥레이서만 했던것 같네요.</p>

<p><img src="/assets/images/2022-08-30/deepracer-2018.png" alt="deepracer" /></p>

<h2 id="2019년-1월-deepracer-with-robomaker---aws-community-day-2019">2019년 1월 DeepRacer with RoboMaker - AWS Community Day 2019</h2>

<p>AWS DeepRacer 는 발표했지만, 아직 요즘과 같은 DeepRacer 콘솔은 존재 하지 않았습니다. <code class="language-plaintext highlighter-rouge">Amazon SageMaker</code> 와 <code class="language-plaintext highlighter-rouge">AWS RoboMaker</code> 를 이용해서 훈련을 해야 했고, <code class="language-plaintext highlighter-rouge">AWS Community Day 2019</code> 에서 훈련 하는 방법을 발표 했습니다.</p>

<div class="youtube-wrapper">
  <iframe src="https://www.youtube.com/embed/v5GBUpVkZbY" allowfullscreen=""></iframe>
</div>

<h2 id="2019년-8월-github-circleci-argo-cd-를-이용한-kubernetes-cicd-파이프라인-구축">2019년 8월 <a href="https://www.meetup.com/ko-KR/awskrug/events/263607584/">Github, CircleCi, Argo-CD 를 이용한 Kubernetes CI/CD 파이프라인 구축</a></h2>

<p>AWS DeepRacer 외에 본 직업이 DevOps Engineer 이자, <code class="language-plaintext highlighter-rouge">IaC</code> 와 <code class="language-plaintext highlighter-rouge">GitOps</code> 를 아우르는 <code class="language-plaintext highlighter-rouge">EaC (Everything as Code)</code> 신봉자 이므로 <code class="language-plaintext highlighter-rouge">GitOps</code> 관련 발표도 했습니다.</p>

<h2 id="2019년-11월-딥레이서-타이머-및-센서">2019년 11월 딥레이서 타이머 및 센서</h2>

<p>국내에서 열리는 AWS DeepRacer 경기를 참여하면서 랩타임이 사람손에 의해서 정확하지 않다라는 생각을 하게 되었습니다. 그래서 <code class="language-plaintext highlighter-rouge">압력감지 센서</code> 와 <code class="language-plaintext highlighter-rouge">라즈베리파이</code> 를 이용해서 <a href="https://nalbam.github.io/2019/11/07/deepracer-timer-ko.html">딥레이서 타이머</a> <a href="https://nalbam.github.io/2019/11/07/deepracer-timer-en.html">🇺🇸</a> 를 제작하였습니다. 많은 분들이 좋아해 주셨고, AWS 에서도 이걸 참고해서 만들고 계시던데.. 이후 소식이 궁금 하네요. ㅎㅎ</p>

<div class="youtube-wrapper">
  <iframe src="https://www.youtube.com/embed/E9f_TfVdgaY" allowfullscreen=""></iframe>
</div>

<h2 id="2019년-11월-aws-deepracer-championship-2019---aws-reinvent-2019">2019년 11월 AWS DeepRacer Championship 2019 - AWS re:Invent 2019</h2>

<p><code class="language-plaintext highlighter-rouge">AWS Summit Seoul 2019</code> 에서 딥레이서 경기가 있었고, 1등에게는 AWS DeepRacer Championship 2019 이 주어지지만 저는 10등에 머무르고 말았습니다. 하지만 매월 열리는 딥레이서 가상 리그에 도전을 했고, 포인트를 모아 결국 <code class="language-plaintext highlighter-rouge">AWS DeepRacer Championship 2019</code> 에 진출 할수 있게 되었습니다. DeepRacer Championship 진출자에게는 <code class="language-plaintext highlighter-rouge">AWS re:Invent 2019</code> 가 열리는 라스베가스로의 왕복 항공권, 호텔 숙박권 및 리인벤트 입장권이 주어졌습니다. 하지만 안타깝게도 16강에는 들지 못했습니다. ㅠㅠ</p>

<p><img src="/assets/images/2022-08-30/deepracer_MGM_Grand_Arena.jpg" alt="AWS DeepRacer Championship 2019" /></p>

<h2 id="2020년-2월-딥레이서-리그에-도전하세요---aws-community-day-2020">2020년 2월 딥레이서 리그에 도전하세요 - AWS Community Day 2020</h2>

<p><code class="language-plaintext highlighter-rouge">AWS Community Day 2020</code> 에서는 지난 AWS DeepRacer Championship 2019 의 경험을 발표하는 발표를 진행했습니다. 동시에 딥레이서 리그도 진행 했습니다.</p>

<div class="youtube-wrapper">
  <iframe src="https://www.youtube.com/embed/BdJKSpothPk" allowfullscreen=""></iframe>
</div>

<h2 id="2020년-9월-딥레이서-도전하기-라이브">2020년 9월 딥레이서 도전하기 라이브</h2>

<p><code class="language-plaintext highlighter-rouge">COVID-19</code> 로 인하여 오프라인 밋업들이 모두 중단되었습니다. 그래서 AWSKRUG DeepRacer 소모임은 온라인으로 진행 했는데요. 3번의 DeepRacer 밋업을 유튜브 라이브 스크리밍으로 진행 했습니다.</p>

<div class="youtube-wrapper">
  <iframe src="https://www.youtube.com/embed/LXepqppNmBM" allowfullscreen=""></iframe>
</div>

<div class="youtube-wrapper">
  <iframe src="https://www.youtube.com/embed/Y5qen_ySiHQ" allowfullscreen=""></iframe>
</div>

<div class="youtube-wrapper">
  <iframe src="https://www.youtube.com/embed/lCxqupveSGQ" allowfullscreen=""></iframe>
</div>

<h2 id="2020년-10월-딥레이서-커뮤니티데이-리그-시상식---aws-community-day-online-2020">2020년 10월 딥레이서 커뮤니티데이 리그 시상식 - AWS Community Day online 2020</h2>

<p>온라인으로 열린 <code class="language-plaintext highlighter-rouge">AWS Community Day online 2020</code> 에서는 딥레이서 커뮤니티 리그를 온라인으로 진행 했습니다. 이 시상식을 위해.. <a href="https://dracer.io/league/community-day-2020">딥레이서 리그 리더보드</a>를 만들고, 꽃가루 효과도 주고, 음악과 박수 소리도 입히는 작업도 했네요.</p>

<div class="youtube-wrapper">
  <iframe src="https://www.youtube.com/embed/c96rYjRa6Tw" allowfullscreen=""></iframe>
</div>

<h2 id="2020년-10월-aws-deepracer-hero-선정">2020년 10월 AWS DeepRacer Hero 선정</h2>

<p>감사하게도 한국에서 10번째로 <a href="https://aws.amazon.com/ko/blogs/korea/aws-deepracer-hero-jungyoul-yu/">AWS DeepRacer Hero</a> 로 선정 되었습니다.</p>

<h2 id="2020년-11월-aws-deepracer-championship-2020---aws-reinvent-2020">2020년 11월 AWS DeepRacer Championship 2020 - AWS re:Invent 2020</h2>

<p>2020년 8월 <code class="language-plaintext highlighter-rouge">AWS DeepRacer 2020 Virtual Circuit OA (Object avoidance)</code> 리그에서 1등을 하여 Championship 에 진출 하게 되었습니다.
하지만 COVID-19 로 인하여 <code class="language-plaintext highlighter-rouge">AWS re:Invent 2020</code> 역시 온라인으로 개최되었고, <code class="language-plaintext highlighter-rouge">AWS DeepRacer Championship 2020</code> 또한 온라인으로 진행 되었습니다.</p>

<div class="youtube-wrapper">
  <iframe src="https://www.youtube.com/embed/Ka5V4ylDuWw" allowfullscreen=""></iframe>
</div>

<p><code class="language-plaintext highlighter-rouge">5분 40초</code> 부터 RayG 를 누르고 16강에 진출하는 모습을 볼 수 있습니다.</p>

<h2 id="2021년-8월-아마존-eks-업그레이드---당근-sre-밋업-1회">2021년 8월 아마존 EKS 업그레이드 - 당근 SRE 밋업 1회</h2>

<p>현직장인 <code class="language-plaintext highlighter-rouge">당근마켓</code> 에서는 대부분의 워크로드를 AWS 에서 운영하고, 대부분의 서비스가 컨테이너화 되어 <code class="language-plaintext highlighter-rouge">Amazon EKS</code> 위에서 구동 되고있습니다. Kubernetes 의 버전이 올라감에 따라 Amazon EKS 의 Kubernetes 버전도 올려줘야 하는데요. <code class="language-plaintext highlighter-rouge">당근 SRE 밋업 1회</code> 에서 안정적으로 업그레이드 했던 경험을 공유 하는 시간을 가졌습니다.</p>

<div class="youtube-wrapper">
  <iframe src="https://www.youtube.com/embed/4Q8It_UvIws" allowfullscreen=""></iframe>
</div>

<h2 id="2021년-10월-aws-deepracer-훈련하고-리그에-도전하기---aws-community-day-2021">2021년 10월 AWS DeepRacer 훈련하고 리그에 도전하기 - AWS Community Day 2021</h2>

<p><code class="language-plaintext highlighter-rouge">AWS Community Day 2021</code> 에서도 딥레이서를 소개하고, 훈련하는 방법을 소개하는 발표를 했습니다. AWS Community Day 2021 의 운영진들은 당근마켓 라운지에서 진행했고, 딥레이서 리그는 온라인으로 진행 했는데… 무선연결이 좋지 않아 화면이 끊기는 방송사고도 있었습니다. ㅠㅠ</p>

<div class="youtube-wrapper">
  <iframe src="https://www.youtube.com/embed/TYn39z_j_Gs" allowfullscreen=""></iframe>
</div>

<h2 id="2021년-11월-aws-reinvent-2021">2021년 11월 AWS re:Invent 2021</h2>

<p>현장에서 3번째로 참가하는 <code class="language-plaintext highlighter-rouge">AWS re:Invent 2021</code> 은 <code class="language-plaintext highlighter-rouge">AWS Hero</code> 로써 참여 할수 있었습니다. 리인벤트 기간내내 딥레이서 트랙 주변만 어슬렁 거렸던것 같네요. ㅎㅎ</p>

<p><img src="/assets/images/2022-08-30/reinvent-2021.png" alt="Welcome to Las Vegas" /></p>

<h2 id="2021년-12월-쿠버네티스-멀티-클러스터에서-addons를-관리하는-방법---당근-sre-밋업-2회">2021년 12월 쿠버네티스 멀티 클러스터에서 Addons를 관리하는 방법 - 당근 SRE 밋업 2회</h2>

<p><code class="language-plaintext highlighter-rouge">당근마켓</code> 글로벌 서비스로 한국, 일본, 영국, 캐나다 리전에서 <code class="language-plaintext highlighter-rouge">Amazon EKS</code> 위에서 구동 되고있습니다. 여러 분산된 클러스터를 동일한 환경에서 관리하기 위한 방법을 당근 <code class="language-plaintext highlighter-rouge">SRE 밋업 2회</code> 에서 소개 했습니다.</p>

<div class="youtube-wrapper">
  <iframe src="https://www.youtube.com/embed/tqyj1klizCU" allowfullscreen=""></iframe>
</div>

<h2 id="2022년">2022년</h2>

<p>2022년에는 밋업 발표가 없군요. 다만 4월과 5월에 딥레이서 온라인 리그를 열긴 했지만요. 다시한번 신발끈을 조여 봐야겠네요.</p>]]></content><author><name></name></author><category term="aws" /><category term="awskorea" /><category term="awskorea10th" /><category term="awskrug" /><summary type="html"><![CDATA[2022년 올해로 AWS Korea 가 10주년 이라고 합니다. AWS Korea 10주년을 축하하며, 제가 AWS 와 함께했던 기억을 더듬어 보려고 합니다.]]></summary></entry><entry><title type="html">Docker builx - multi architecture</title><link href="https://nalbam.github.io//2021/09/03/docker-builx.html" rel="alternate" type="text/html" title="Docker builx - multi architecture" /><published>2021-09-03T00:00:00+00:00</published><updated>2021-09-03T00:00:00+00:00</updated><id>https://nalbam.github.io//2021/09/03/docker-builx</id><content type="html" xml:base="https://nalbam.github.io//2021/09/03/docker-builx.html"><![CDATA[<p>우리가 사용하는 서버는 대부분 Intel core 기반의 <code class="language-plaintext highlighter-rouge">amd64</code> 에요. 따라서 대부분의 컨테이너 이미지는 amd64 기준으로 빌드되고 있어요. 하지만 프로세서는 arm 도 있고, 특히 AWS Graviton 2 는 <code class="language-plaintext highlighter-rouge">arm64</code> 코어를 기반으로 동작하고 되어있어요.</p>

<p>그래서 이미지를 멀티 아키텍처로 빌드하고, docker hub 와 aws ecr 에 push 하는 방법을 알아 보았어요.</p>

<h2 id="docker-manifest">Docker Manifest</h2>

<p>첫번째 방법은 <code class="language-plaintext highlighter-rouge">docker manifest</code> 를 이용하는 방법 이에요. docker build 를 각 arch 별로 진행하고, manifest 로 묶어주는 방법 이에요.</p>

<blockquote>
  <p>각각 빌드하고, manifest 로 묶어주는 작업이 필요 하더라고요.</p>
</blockquote>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># AMD64</span>
<span class="nv">$ </span>docker build <span class="nt">-t</span> daangn/app:tag-amd64 <span class="nt">--build-arg</span> <span class="nv">ARCH</span><span class="o">=</span>amd64/ <span class="nb">.</span>
<span class="nv">$ </span>docker push daangn/app:tag-amd64

<span class="c"># ARM64</span>
<span class="nv">$ </span>docker build <span class="nt">-t</span> daangn/app:tag-arm64 <span class="nt">--build-arg</span> <span class="nv">ARCH</span><span class="o">=</span>arm64/ <span class="nb">.</span>
<span class="nv">$ </span>docker push daangn/app:tag-arm64

<span class="nv">$ </span>docker manifest create daangn/app:tag-latest <span class="se">\</span>
  <span class="nt">--amend</span> daangn/app:tag-amd64 <span class="se">\</span>
  <span class="nt">--amend</span> daangn/app:tag-arm64

<span class="nv">$ </span>docker manifest push daangn/app:tag-latest
</code></pre></div></div>

<h2 id="docker-buildx">Docker Buildx</h2>

<p>두번째 방법은 <code class="language-plaintext highlighter-rouge">docker buildx</code> 를 이용하는 방법 이에요. buildx 는 별도로 설치 해줘야 해요.</p>

<p>이렇게 하면 push 까지 잘 되요.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>docker buildx create <span class="nt">--use</span> <span class="nt">--name</span> daangn

<span class="nv">$ </span>docker buildx build <span class="se">\</span>
  <span class="nt">--push</span> <span class="se">\</span>
  <span class="nt">--platform</span> linux/amd64,linux/arm64 <span class="se">\</span>
  <span class="nt">--tag</span> daangn/app:tag <span class="nb">.</span>

<span class="nv">$ </span>docker buildx imagetools inspect daangn/app:tag
</code></pre></div></div>

<h2 id="install-buildx">Install Buildx</h2>

<p>아래 방법은 <code class="language-plaintext highlighter-rouge">Mac</code> 또는 <code class="language-plaintext highlighter-rouge">CircleCi</code> 에서 사용 할수 있는 방법 이에요.</p>

<p>바이너리 파일은 <a href="https://github.com/docker/buildx/releases">여기</a>에서 버전 확인 및 다운 받아요.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> <span class="nt">-p</span> ~/.docker/cli-plugins
<span class="nv">url</span><span class="o">=</span><span class="s2">"https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.darwin-amd64"</span>
curl <span class="nt">-sSL</span> <span class="nt">-o</span> ~/.docker/cli-plugins/docker-buildx <span class="k">${</span><span class="nv">url</span><span class="k">}</span>
<span class="nb">chmod </span>a+x ~/.docker/cli-plugins/docker-buildx
</code></pre></div></div>

<p>아래 방법은 docker builder 이미지 만들때 사용 하는 방법 이에요.</p>

<p>제 <a href="https://github.com/marketplace/actions/docker-push">Github Action</a> 을 이렇게 구현 했어요.</p>

<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> docker</span>
<span class="k">COPY</span><span class="s"> --from=docker/buildx-bin /buildx /usr/libexec/docker/cli-plugins/docker-buildx</span>
<span class="k">RUN </span>docker buildx version
</code></pre></div></div>

<p>제가 Github Action 에서 사용하는 <a href="https://github.com/opspresso/builder/blob/master/Dockerfile">Dockerfile</a> 이에요.</p>

<h2 id="docker-push">Docker Push</h2>

<p><a href="https://hub.docker.com/repository/docker/nalbam/sample-node/tags?page=1&amp;ordering=last_updated">도커 허브</a>에 올라간 이미지에요.</p>

<p><img src="/assets/images/2021-09-03/hub.png" alt="docker-hub" /></p>

<p><code class="language-plaintext highlighter-rouge">AWS ECR </code>에 올라간 이미지 에요. (untagged 도 생기더라고요.)</p>

<p><img src="/assets/images/2021-09-03/ecr.png" alt="aws-ecr" /></p>

<h2 id="github-action">Github Action</h2>

<ul>
  <li><a href="https://github.com/marketplace/actions/docker-push">Docker Push :: Github Action</a> <a href="https://github.com/opspresso/action-docker/releases"><img src="https://img.shields.io/github/release/opspresso/action-docker.svg" alt="GitHub Releases" /></a></li>
</ul>

<p>Docker Hub, AWS ECR (Private, Public), Quay.io, Github Package 에서 테스트 되었어요.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Docker Push</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="na">push</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">main</span>
      <span class="pi">-</span> <span class="s">master</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">docker</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v3</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">fetch-depth</span><span class="pi">:</span> <span class="m">1</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build &amp; Push to Docker Hub</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">opspresso/action-docker@master</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">args</span><span class="pi">:</span> <span class="s">--docker</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">USERNAME</span><span class="pi">:</span> <span class="s">secrets.DOCKER_USERNAME</span>
          <span class="na">PASSWORD</span><span class="pi">:</span> <span class="s">secrets.DOCKER_PASSWORD</span>
          <span class="na">TAG_NAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">v0.0.1"</span>
          <span class="na">BUILDX</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build &amp; Push to Quay.io</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">opspresso/action-docker@master</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">args</span><span class="pi">:</span> <span class="s">--docker</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">USERNAME</span><span class="pi">:</span> <span class="s">secrets.QUAY_USERNAME</span>
          <span class="na">PASSWORD</span><span class="pi">:</span> <span class="s">secrets.QUAY_PASSWORD</span>
          <span class="na">REGISTRY</span><span class="pi">:</span> <span class="s2">"</span><span class="s">quay.io"</span>
          <span class="na">TAG_NAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">v0.0.1"</span>
          <span class="na">BUILDX</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build &amp; Push to GitHub Package</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">opspresso/action-docker@master</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">args</span><span class="pi">:</span> <span class="s">--docker</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">PASSWORD</span><span class="pi">:</span> <span class="s">secrets.GHP_TOKEN</span>
          <span class="na">REGISTRY</span><span class="pi">:</span> <span class="s2">"</span><span class="s">docker.pkg.github.com"</span>
          <span class="na">TAG_NAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">v0.0.1"</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build &amp; Push to AWS ECR Private</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">opspresso/action-docker@master</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">args</span><span class="pi">:</span> <span class="s">--ecr</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">AWS_ACCESS_KEY_ID</span><span class="pi">:</span> <span class="s">secrets.AWS_ACCESS_KEY_ID</span>
          <span class="na">AWS_SECRET_ACCESS_KEY</span><span class="pi">:</span> <span class="s">secrets.AWS_SECRET_ACCESS_KEY</span>
          <span class="na">AWS_REGION</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ap-northeast-2"</span>
          <span class="na">TAG_NAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">v0.0.1"</span>
          <span class="na">BUILDX</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build &amp; Push to AWS ECR Public</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">opspresso/action-docker@master</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">args</span><span class="pi">:</span> <span class="s">--ecr</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">AWS_ACCESS_KEY_ID</span><span class="pi">:</span> <span class="s">secrets.AWS_ACCESS_KEY_ID</span>
          <span class="na">AWS_SECRET_ACCESS_KEY</span><span class="pi">:</span> <span class="s">secrets.AWS_SECRET_ACCESS_KEY</span>
          <span class="na">AWS_REGION</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ap-northeast-2"</span>
          <span class="na">REGISTRY</span><span class="pi">:</span> <span class="s2">"</span><span class="s">public.ecr.aws/nalbam"</span>
          <span class="na">TAG_NAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">v0.0.1"</span>
          <span class="na">BUILDX</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
</code></pre></div></div>

<h2 id="github-action-빌드-로그">Github Action 빌드 로그</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># [docker] start...

$ docker login  -u ***
WARNING! Your password will be stored unencrypted in /github/home/.docker/config.json.
Login Succeeded
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store


$ docker buildx create --use --name ops-1630641363
ops-1630641363

$ docker buildx build  -t ***/sample-node:v0.8.56 -f Dockerfile .
#1 [internal] booting buildkit
#1 pulling image moby/buildkit:buildx-stable-1
#1 pulling image moby/buildkit:buildx-stable-1 4.6s done
#1 creating container buildx_buildkit_ops-16306413630
#1 creating container buildx_buildkit_ops-16306413630 0.4s done
#1 DONE 5.0s

#2 [internal] load build definition from Dockerfile
#2 transferring dockerfile: 203B done
#2 DONE 0.0s

#3 [internal] load .dockerignore
#3 transferring context: 54B done
#3 DONE 0.0s

#5 [linux/arm64 internal] load metadata for docker.io/library/node:13-alpine
#5 ...

#6 [auth] library/node:pull token for registry-1.docker.io
#6 DONE 0.0s

#4 [linux/amd64 internal] load metadata for docker.io/library/node:13-alpine
#4 ...

#7 [auth] library/node:pull token for registry-1.docker.io
#7 DONE 0.0s

#4 [linux/amd64 internal] load metadata for docker.io/library/node:13-alpine

#9 [linux/amd64 2/4] WORKDIR /data
#9 DONE 1.9s

#15 [linux/arm64 3/4] COPY ./entrypoint.sh /data/entrypoint.sh
#15 DONE 0.0s

#11 [linux/amd64 3/4] COPY ./entrypoint.sh /data/entrypoint.sh
#11 DONE 0.0s

#12 [linux/amd64 4/4] ADD . /data
#12 DONE 2.5s

#16 [linux/arm64 4/4] ADD . /data
#16 DONE 2.5s

#17 exporting to image
#17 exporting layers
#17 exporting layers 6.2s done
#17 exporting manifest sha256:6ff451754b9cc68a11b044f99f52969faaf4ac389f940dd610c341665a0d92e8 done
#17 exporting config sha256:67930b934cd15c44f91d0c9e623cc6c5507738f3a6be637d9a4d18405a9c5e2f done
#17 exporting manifest sha256:3e8eaa2b33557e3ecff57eeb936af8e19058788af2a0a9e2cda45faf8e23057f done
#17 exporting config sha256:d7f620c0af01e1242d678c5dde51806d592ec65e3bc15ff1ae1a6b352fc7c5f4 done
#17 exporting manifest list sha256:1c9e2ac294028c76a842a858710e0d46b3851496c0eca91271e3cdf1518aa3c7 done
#17 pushing layers
#17 ...

#18 [auth] ***/sample-node:pull,push token for registry-1.docker.io
#18 DONE 0.0s

#17 exporting to image
#17 ...

#19 [auth] ***/sample-node:pull,push token for registry-1.docker.io
#19 DONE 0.0s

#17 exporting to image
#17 pushing layers 3.3s done
#17 pushing manifest for docker.io/***/sample-node:v0.8.56@sha256:1c9e2ac294028c76a842a858710e0d46b3851496c0eca91271e3cdf1518aa3c7
#17 pushing manifest for docker.io/***/sample-node:v0.8.56@sha256:1c9e2ac294028c76a842a858710e0d46b3851496c0eca91271e3cdf1518aa3c7 1.0s done
#17 DONE 10.6s

$ docker buildx imagetools inspect ***/sample-node:v0.8.56
Name:      docker.io/***/sample-node:v0.8.56
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
Digest:    sha256:1c9e2ac294028c76a842a858710e0d46b3851496c0eca91271e3cdf1518aa3c7

Manifests:
  Name:      docker.io/***/sample-node:v0.8.56@sha256:6ff451754b9cc68a11b044f99f52969faaf4ac389f940dd610c341665a0d92e8
  MediaType: application/vnd.docker.distribution.manifest.v2+json
  Platform:  linux/arm64

  Name:      docker.io/***/sample-node:v0.8.56@sha256:3e8eaa2b33557e3ecff57eeb936af8e19058788af2a0a9e2cda45faf8e23057f
  MediaType: application/vnd.docker.distribution.manifest.v2+json
  Platform:  linux/amd64

$ docker logout
Removing login credentials for https://index.docker.io/v1/
</code></pre></div></div>

<h2 id="amazone-eks-배포">Amazone EKS 배포</h2>

<p>Amazon EKS <code class="language-plaintext highlighter-rouge">1.21</code> 에 인스턴스 타입이 <code class="language-plaintext highlighter-rouge">c6g.large</code> 인 nodegroup <code class="language-plaintext highlighter-rouge">graviton</code> 을 만들고 <code class="language-plaintext highlighter-rouge">taints</code> 를 걸어 주었어요.</p>

<p><img src="/assets/images/2021-09-03/node.png" alt="kubectl-get-node" /></p>

<p>그리고 해당 노드그룹에 배포 했을때, 아키텍처에 맞는 image 를 pull 도 잘 하고 실행도 잘 되었어요.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">sample-node</span>
    <span class="na">app.kubernetes.io/instance</span><span class="pi">:</span> <span class="s">sample-node-eks-demo</span>
    <span class="na">app.kubernetes.io/name</span><span class="pi">:</span> <span class="s">sample-node</span>
    <span class="na">version</span><span class="pi">:</span> <span class="s">v0.8.56</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">sample-node</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">default</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">revisionHistoryLimit</span><span class="pi">:</span> <span class="m">0</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">matchLabels</span><span class="pi">:</span>
      <span class="na">app.kubernetes.io/instance</span><span class="pi">:</span> <span class="s">sample-node-eks-demo</span>
      <span class="na">app.kubernetes.io/name</span><span class="pi">:</span> <span class="s">sample-node</span>
  <span class="na">template</span><span class="pi">:</span>
    <span class="na">metadata</span><span class="pi">:</span>
      <span class="na">labels</span><span class="pi">:</span>
        <span class="na">app</span><span class="pi">:</span> <span class="s">sample-node</span>
        <span class="na">app.kubernetes.io/instance</span><span class="pi">:</span> <span class="s">sample-node-eks-demo</span>
        <span class="na">app.kubernetes.io/name</span><span class="pi">:</span> <span class="s">sample-node</span>
        <span class="na">version</span><span class="pi">:</span> <span class="s">v0.8.56</span>
    <span class="na">spec</span><span class="pi">:</span>
      <span class="na">containers</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">app</span>
          <span class="na">image</span><span class="pi">:</span> <span class="s">nalbam/sample-node:v0.8.56</span>
          <span class="na">ports</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">3000</span>
              <span class="na">name</span><span class="pi">:</span> <span class="s">http</span>
              <span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span>
      <span class="na">nodeSelector</span><span class="pi">:</span>
        <span class="na">group</span><span class="pi">:</span> <span class="s">graviton</span>
      <span class="na">tolerations</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">effect</span><span class="pi">:</span> <span class="s">NoSchedule</span>
          <span class="na">key</span><span class="pi">:</span> <span class="s">group</span>
          <span class="na">operator</span><span class="pi">:</span> <span class="s">Equal</span>
          <span class="na">value</span><span class="pi">:</span> <span class="s">graviton</span>
</code></pre></div></div>]]></content><author><name></name></author><category term="docker" /><category term="buildx" /><category term="multi" /><category term="architecture" /><category term="amd64" /><category term="arm64" /><summary type="html"><![CDATA[우리가 사용하는 서버는 대부분 Intel core 기반의 amd64 에요. 따라서 대부분의 컨테이너 이미지는 amd64 기준으로 빌드되고 있어요. 하지만 프로세서는 arm 도 있고, 특히 AWS Graviton 2 는 arm64 코어를 기반으로 동작하고 되어있어요.]]></summary></entry><entry><title type="html">AWS 클라우드, 라즈베리파이, 온도센서를 활용한 체온 알람 서비스 v2</title><link href="https://nalbam.github.io//2020/06/19/body-temperature-alarm-v2.html" rel="alternate" type="text/html" title="AWS 클라우드, 라즈베리파이, 온도센서를 활용한 체온 알람 서비스 v2" /><published>2020-06-19T00:00:00+00:00</published><updated>2020-06-19T00:00:00+00:00</updated><id>https://nalbam.github.io//2020/06/19/body-temperature-alarm-v2</id><content type="html" xml:base="https://nalbam.github.io//2020/06/19/body-temperature-alarm-v2.html"><![CDATA[<p>이전 포스팅에서 <a href="https://nalbam.github.io/2020/02/28/body-temperature-alarm-ko.html">AWS 클라우드, 라즈베리파이, 온도센서를 활용한 체온 알람 서비스</a> 를 소개 했습니다.</p>

<p>위의 체온카메라는 <a href="https://www.adafruit.com/product/3538">Adafruit AMG8833 IR Thermal Camera Breakout</a> 으로 저렴한 가격이지만 성능은 8x8 픽셀로 그리 좋은편은 아니었습니다.</p>

<p>그래서 더 비싸고, 더 좋은 성능의 <a href="https://groupgets.com/manufacturers/flir/products/lepton-3-5">FLIR Lepton 3.5</a> 로 업그레이드 하고, 라즈베리파이 4로 업그레이드 했습니다.</p>

<p><img src="/assets/images/2020-06-19/doorman-v2.jpg" alt="doorman" /></p>

<h2 id="raspberry-pi">Raspberry Pi</h2>

<p>먼저 라즈베리파이는 두개의 앱이 동작 하는데요.</p>

<p><a href="https://github.com/nalbam/LeptonModule">립톤 카메라</a> 는 립톤이 제공하는 샘플 코드를 조금 손봐서 작성 했습니다. 열감지 센서가 리턴하는 값을 섭씨 온도로 바꿔주고, 일정 온도 이상이거나, 버튼을 누르면, 열화상을 이미지로 변환하여, <a href="https://aws.amazon.com/ko/s3/">Amazon S3</a> Bucket 에 업로드 하고, 체온 및 기기의 정보를 json에 저장 하여 업로드 합니다.</p>

<p><a href="https://github.com/nalbam/rpi-doorman">파이 카메라</a> 는 열화상 이미지가 생성되면, 일반사진을 캡쳐하어 업로드 하고 있습니다.</p>

<h2 id="lambda-backend">Lambda Backend</h2>

<p><a href="https://github.com/nalbam/deeplens-doorman-backend">벡엔드 서비스</a> 에서는 <a href="https://aws.amazon.com/ko/s3/">Amazon S3</a> Bucket 에 일반사진과 열사진 그리고 카메라 위치 및 온도 정보가 업로드 되면, <a href="https://aws.amazon.com/ko/lambda/">AWS Lambda Function</a> 가 <a href="https://aws.amazon.com/ko/rekognition/">Amazon Rekognition</a> 으로 안면인식을 수행하여 방문자로 <a href="https://aws.amazon.com/ko/dynamodb/">Amazon DynamoDB</a> 에 저장 합니다.</p>

<h2 id="amplify-frontend">Amplify Frontend</h2>

<p><a href="https://aws.amazon.com/ko/amplify/">AWS Amplify</a> 프레임웍으로 작성된 <a href="https://github.com/nalbam/doorman">프론트 웹서비스</a> 에서는 벡엔드로 부터 최근 접속했던 정보를 받아, 방문자의 정보를 보여줍니다.</p>

<p><img src="/assets/images/2020-06-19/frontend.jpg" alt="frontend" /></p>

<h2 id="update">Update</h2>

<p>하드웨어 업데이트와 함께 소프트웨어를 변경 했습니다.</p>

<p>여러위치에 기기를 설치 할 수 있도록 했습니다. 온도와 열사진 및 일반사진을 각각의 위치 정보와 함께 기록 합니다. 일반사진을 안면인식을 통해 방문자 별로 기록하고, 현재 방문자가 최근 한달 이내에 어디에 방문했는지 확인 할 수 있습니다.</p>

<h2 id="architecture">Architecture</h2>

<p><img src="/assets/images/2020-06-19/doorman-arch.jpg" alt="doorman-arch" /></p>

<p>감사합니다.</p>]]></content><author><name></name></author><category term="thermal-camera" /><category term="raspberry-pi" /><category term="covid-19" /><summary type="html"><![CDATA[이전 포스팅에서 AWS 클라우드, 라즈베리파이, 온도센서를 활용한 체온 알람 서비스 를 소개 했습니다.]]></summary></entry><entry><title type="html">Terraform Cloud</title><link href="https://nalbam.github.io//2020/05/20/terraform-cloud.html" rel="alternate" type="text/html" title="Terraform Cloud" /><published>2020-05-20T00:00:00+00:00</published><updated>2020-05-20T00:00:00+00:00</updated><id>https://nalbam.github.io//2020/05/20/terraform-cloud</id><content type="html" xml:base="https://nalbam.github.io//2020/05/20/terraform-cloud.html"><![CDATA[<p>얼마전까지 <a href="https://app.terraform.io/">Terraform Cloud</a> 에서 관리 할 수 있는 워크스페이스가 2개 인줄 알고 있었으나, 무료 사용의 경우도 무제한 이라고 합니다.</p>

<p>무료 요금제의 경우 팀원에 5명까지 넣을수 있습니다. 다만 모두 <code class="language-plaintext highlighter-rouge">owner</code> 권한 입니다.</p>

<p><code class="language-plaintext highlighter-rouge">$20/user</code> 요금제를 선택해야 권한 제어가 가능 하다고 합니다.
그래도 소규모 팀에서는 권한제어가 되지 않아도 무제한 워크스페이스니 변경하는 것도 좋을것 같네요.</p>

<p><img src="/assets/images/2020-05-20/free.png" alt="Unlimited Workspaces" /></p>

<p>그래서 테스트용으로 관리/유지 하던 코드를 <code class="language-plaintext highlighter-rouge">Terraform Cloud</code> 로 옮기는 작업을 했습니다.</p>

<p><img src="/assets/images/2020-05-20/workspaces.png" alt="DEMO Workspaces" /></p>

<p>Terraform Cloud 로 이전 했던 내용들을 공유 합니다.</p>

<h1 id="연결-끊기">연결 끊기</h1>

<p>우선 기존의 리소스는 <code class="language-plaintext highlighter-rouge">AWS S3</code> 에 저장되고 있습니다.
또한 <code class="language-plaintext highlighter-rouge">vpc_id</code>, <code class="language-plaintext highlighter-rouge">subnet_id</code> 값을 얻기 위해 <code class="language-plaintext highlighter-rouge">remote_state</code> 를 사용하고 있습니다.</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">terraform</span> <span class="p">{</span>
  <span class="nx">backend</span> <span class="s2">"s3"</span> <span class="p">{</span>
    <span class="nx">region</span>         <span class="p">=</span> <span class="s2">"ap-northeast-2"</span>
    <span class="nx">bucket</span>         <span class="p">=</span> <span class="s2">"terraform-demo-seoul"</span>
    <span class="nx">key</span>            <span class="p">=</span> <span class="s2">"bastion.tfstate"</span>
    <span class="nx">dynamodb_table</span> <span class="p">=</span> <span class="s2">"terraform-demo-seoul"</span>
    <span class="nx">encrypt</span>        <span class="p">=</span> <span class="kc">true</span>
  <span class="p">}</span>
  <span class="nx">required_version</span> <span class="p">=</span> <span class="s2">"&gt;= 0.12"</span>
<span class="p">}</span>

<span class="nx">data</span> <span class="s2">"terraform_remote_state"</span> <span class="s2">"vpc"</span> <span class="p">{</span>
  <span class="nx">backend</span> <span class="p">=</span> <span class="s2">"s3"</span>
  <span class="nx">config</span> <span class="p">=</span> <span class="p">{</span>
    <span class="nx">region</span> <span class="p">=</span> <span class="s2">"ap-northeast-2"</span>
    <span class="nx">bucket</span> <span class="p">=</span> <span class="s2">"terraform-demo-seoul"</span>
    <span class="nx">key</span>    <span class="p">=</span> <span class="s2">"vpc-demo.tfstate"</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>S3 와의 연결을 끊기위해 위의 설정을 모두 주석 처리 합니다.
S3 에서 <code class="language-plaintext highlighter-rouge">bastion.tfstate</code> 을 다운받아 소스 디렉토리에 <code class="language-plaintext highlighter-rouge">terraform.tfstate</code> 으로 저장 합니다.</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="c1"># vpc_id = data.terraform_remote_state.vpc.outputs.vpc_id</span>
  <span class="nx">vpc_id</span> <span class="err">=</span> <span class="s2">"vpc-0c595d8f4301b48c5"</span>

  <span class="c1"># subnet_id = data.terraform_remote_state.vpc.outputs.public_subnet_ids[0]</span>
  <span class="nx">subnet_id</span> <span class="err">=</span> <span class="s2">"subnet-0996a66652b801ce0"</span>
</code></pre></div></div>

<p>그리고 <code class="language-plaintext highlighter-rouge">remote_state</code> 에서 얻어오던 <code class="language-plaintext highlighter-rouge">vpc_id</code>, <code class="language-plaintext highlighter-rouge">subnet_id</code> 를 실제 값으로 입력 합니다.</p>

<p>이제 모든 원격 연결이 끊어졌습니다.</p>

<p><img src="/assets/images/2020-05-20/bastion-offline.png" alt="bastion offline" /></p>

<p><code class="language-plaintext highlighter-rouge">terraform plan &amp;&amp; terraform apply</code> 를 해도 변경 사항이 없다는 메세지가 출력 됩니다.</p>

<h1 id="이전-시작">이전 시작</h1>

<p><a href="https://app.terraform.io/">Terraform Cloud</a> 에 접속하여 Organization 을 생성 합니다.</p>

<p><img src="/assets/images/2020-05-20/new-organization.png" alt="New Organization" /></p>

<p>저는 <code class="language-plaintext highlighter-rouge">mzcdev</code> 라고 하겠습니다.</p>

<p><img src="/assets/images/2020-05-20/new-workspace-01.png" alt="New Workspace" /></p>

<p>새로운 워크스페이스를 만들기위해 Github repository 와 연결 합니다.</p>

<p><img src="/assets/images/2020-05-20/new-workspace-02.png" alt="Configure settings" /></p>

<p>선택한 GitHub organization/repository 에 따라 자동으로 이름이 입력 되지만, 저는 <code class="language-plaintext highlighter-rouge">dev-bastion</code> 으로 변경 했습니다.
코드 위치에 따라 <code class="language-plaintext highlighter-rouge">Advanced options</code> 에서 하위 디렉토리를 입력 할 수 있습니다.</p>

<p><img src="/assets/images/2020-05-20/new-workspace-03.png" alt="Environment Variables" /></p>

<p>마지막으로 AWS 리소스 관리 권한 부여를 위해 <code class="language-plaintext highlighter-rouge">Variables</code> 탭에서 <code class="language-plaintext highlighter-rouge">AWS Credentials</code> 을 입력합니다. 민감한 정보는 <code class="language-plaintext highlighter-rouge">Sensitive</code> 를 체크해 주도록 합니다.</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">terraform</span> <span class="p">{</span>
  <span class="nx">backend</span> <span class="s2">"remote"</span> <span class="p">{</span>
    <span class="nx">organization</span> <span class="p">=</span> <span class="s2">"mzcdev"</span>
    <span class="nx">workspaces</span> <span class="p">{</span>
      <span class="nx">name</span> <span class="p">=</span> <span class="s2">"dev-bastion"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>이제 Terraform Cloud 로 이전 하기위해 코드에 위와 같이 <code class="language-plaintext highlighter-rouge">backend remote</code> 를 입력 합니다.
이때 <code class="language-plaintext highlighter-rouge">organization</code> 과 <code class="language-plaintext highlighter-rouge">workspaces name</code> 을 일치 시켜 줍니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>terraform init

Initializing modules...

Initializing the backend...
Acquiring state lock. This may take a few moments...
Do you want to copy existing state to the new backend?
  Pre-existing state was found <span class="k">while </span>migrating the previous <span class="s2">"local"</span> backend to the
  newly configured <span class="s2">"remote"</span> backend. No existing state was found <span class="k">in </span>the newly
  configured <span class="s2">"remote"</span> backend. Do you want to copy this state to the new <span class="s2">"remote"</span>
  backend? Enter <span class="s2">"yes"</span> to copy and <span class="s2">"no"</span> to start with an empty state.

  Enter a value:
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">terraform init</code> 을 하면, local 의 states 를 remote 로 업로드 할 지 물어 봅니다.</p>

<p><img src="/assets/images/2020-05-20/new-workspace-04.png" alt="States" /></p>

<p><code class="language-plaintext highlighter-rouge">yes</code> 를 입력하면 업로드 되고, 웹콘솔에서 확인 할 수 있습니다.</p>

<p>이제 코드를 <code class="language-plaintext highlighter-rouge">commit &amp;&amp; push</code> 합니다.</p>

<p><img src="/assets/images/2020-05-20/queue-plan.png" alt="Queue plan" /></p>

<p><code class="language-plaintext highlighter-rouge">Queue plan</code> 을 실행해 봅니다.</p>

<p><img src="/assets/images/2020-05-20/planned.png" alt="PLANNED" /></p>

<p>변경된 내용이 없으므로 <code class="language-plaintext highlighter-rouge">PLANNED</code> 상태가 되면 정상 입니다.</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">data</span> <span class="s2">"terraform_remote_state"</span> <span class="s2">"vpc"</span> <span class="p">{</span>
  <span class="nx">backend</span> <span class="p">=</span> <span class="s2">"remote"</span>
  <span class="nx">config</span> <span class="p">=</span> <span class="p">{</span>
    <span class="nx">organization</span> <span class="p">=</span> <span class="s2">"mzcdev"</span>
    <span class="nx">workspaces</span> <span class="p">=</span> <span class="p">{</span>
      <span class="nx">name</span> <span class="p">=</span> <span class="s2">"dev-vpc-demo"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nx">vpc_id</span> <span class="err">=</span> <span class="nx">data</span><span class="err">.</span><span class="nx">terraform_remote_state</span><span class="err">.</span><span class="nx">vpc</span><span class="err">.</span><span class="nx">outputs</span><span class="err">.</span><span class="nx">vpc_id</span>

  <span class="nx">subnet_id</span> <span class="err">=</span> <span class="nx">data</span><span class="err">.</span><span class="nx">terraform_remote_state</span><span class="err">.</span><span class="nx">vpc</span><span class="err">.</span><span class="nx">outputs</span><span class="err">.</span><span class="nx">public_subnet_ids</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</code></pre></div></div>

<p>마지막으로 <code class="language-plaintext highlighter-rouge">vpc_id</code>, <code class="language-plaintext highlighter-rouge">subnet_id</code> 정보를 <code class="language-plaintext highlighter-rouge">remote_state</code> 에서 얻어오도록 변경 합니다.</p>

<p>코드를 <code class="language-plaintext highlighter-rouge">commit &amp;&amp; push</code> 합니다.</p>

<p><img src="/assets/images/2020-05-20/planning.png" alt="PLANNING" /></p>

<p>자동으로 <code class="language-plaintext highlighter-rouge">Trigging</code> 되어 <code class="language-plaintext highlighter-rouge">terraform plan</code> 을 수행 합니다.</p>

<p><img src="/assets/images/2020-05-20/done.png" alt="PLANNED" /></p>

<p><code class="language-plaintext highlighter-rouge">PLANNED</code> 상태가 되면 완료 입니다.</p>

<p>감사합니다.</p>]]></content><author><name></name></author><category term="terraform" /><summary type="html"><![CDATA[얼마전까지 Terraform Cloud 에서 관리 할 수 있는 워크스페이스가 2개 인줄 알고 있었으나, 무료 사용의 경우도 무제한 이라고 합니다.]]></summary></entry><entry><title type="html">Body Temperature alarm service using AWS Cloud, Raspberry Pi and Thermal camera</title><link href="https://nalbam.github.io//2020/02/28/body-temperature-alarm-en.html" rel="alternate" type="text/html" title="Body Temperature alarm service using AWS Cloud, Raspberry Pi and Thermal camera" /><published>2020-02-28T00:00:00+00:00</published><updated>2020-02-28T00:00:00+00:00</updated><id>https://nalbam.github.io//2020/02/28/body-temperature-alarm-en</id><content type="html" xml:base="https://nalbam.github.io//2020/02/28/body-temperature-alarm-en.html"><![CDATA[<p>These days, the novel coronavirus (COVID-19) is causing turmoil both domestically and internationally. A large number of confirmed cases have emerged in Korea, and thermal cameras are being installed in many places, including airports and hospitals. However, in many locations, staff must be stationed next to the cameras to monitor them constantly.</p>

<p>As an IT developer, I wanted to create a service that could judge and notify more conveniently, quickly, and accurately using my knowledge.</p>

<p><img src="/assets/images/2020-02-28/doorman.jpg" alt="doorman" /></p>

<h2 id="thermal-camera">Thermal camera</h2>

<p>Higher pixel count and better cameras are expensive, especially since they need to be shipped from China, so I bought a thermal camera with an 8x8 resolution available immediately.</p>

<p><a href="https://www.adafruit.com/product/3538">Adafruit AMG8833 IR Thermal Camera Breakout</a></p>

<p><img src="/assets/images/2020-02-28/amg8833.jpg" alt="amg8833" /></p>

<p><a href="https://learn.adafruit.com/adafruit-amg8833-8x8-thermal-camera-sensor/raspberry-pi-thermal-camera">Sample code made with python + pygame.</a></p>

<h2 id="raspberry-pi">Raspberry pi</h2>

<p>Initially, <a href="https://aws.amazon.com/ko/deeplens/">AWS Deeplens</a> was considered, but the thermal cameras received information through GPIO, so we had to find another alternative. So I chose the Raspberry Pi which was in the desk drawer.</p>

<p>Fortunately, the Raspberry Pi case was compatible with Lego, so the Raspberry camera and thermal camera could be installed on the Lego blocks.</p>

<p><img src="/assets/images/2020-02-28/raspberrypi.jpg" alt="raspberrypi" /></p>

<p>I installed a python program on my Raspberry Pi and gave it permission to upload photos to the <a href="https://aws.amazon.com/ko/s3/">Amazon S3</a>.</p>

<p>For more code, please refer to <a href="https://github.com/nalbam/rpi-doorman">here</a>.</p>

<h2 id="slack-app">Slack App</h2>

<p>In order to receive notifications from Slack, or to save usernames in Slack, Slack App was created according to <a href="https://github.com/nalbam/deeplens-doorman/blob/master/README-slack.md">Settings</a>.</p>

<p><img src="/assets/images/2020-02-28/slack-04.png" alt="slack-04" /></p>

<h2 id="lambda-backend">Lambda Backend</h2>

<p>When the photo is uploaded to Amazon S3 Bucket, <a href="https://aws.amazon.com/ko/lambda/">AWS Lambda Function</a> should be called by <code class="language-plaintext highlighter-rouge">Trigger</code>.
The Lambda function performs facial recognition with <a href="https://aws.amazon.com/ko/rekognition/">Amazon Rekognition</a> and stores them in <a href="https://aws.amazon.com/ko/dynamodb/">Amazon DynamoDB</a> for each person.</p>

<p>This time, I developed and deployed using <a href="https://serverless.com/">Serverless framework</a>.</p>

<p>For more code, please refer to <a href="https://github.com/nalbam/deeplens-doorman-backend">here</a>.</p>

<h2 id="amplify-frontend">Amplify Frontend</h2>

<p>Names and photos stored in DynamoDB are served through the web.
This app was developed and distributed using <a href="https://aws.amazon.com/ko/amplify/">AWS Amplify</a>.</p>

<p>Frontend used <code class="language-plaintext highlighter-rouge">Javascript</code> and <code class="language-plaintext highlighter-rouge">React</code>.
Then, I used the <code class="language-plaintext highlighter-rouge">Rest API</code> to query DynamoDB created in Backend, which was also created with <code class="language-plaintext highlighter-rouge">AWS Lambda Function</code>.</p>

<p>People who were recognized but whose names were unknown were saved as <code class="language-plaintext highlighter-rouge">Unknown</code>. Authentication for the name input form was handled using <a href="https://aws.amazon.com/cognito/">Amazon Cognito</a>. Amplify made it easy to apply login and signup pages without coding them directly.</p>

<p><img src="/assets/images/2020-02-28/doorman-web.jpg" alt="doorman-web" /></p>

<p>For more code, please refer to <a href="https://github.com/nalbam/doorman">here</a>.</p>

<h2 id="architecture">Architecture</h2>

<p><img src="/assets/images/2020-02-28/doorman-arch.jpg" alt="doorman-arch" /></p>

<p>Thanks for reading.</p>]]></content><author><name></name></author><category term="thermal-camera" /><category term="raspberry-pi" /><category term="covid-19" /><summary type="html"><![CDATA[These days, the novel coronavirus (COVID-19) is causing turmoil both domestically and internationally. A large number of confirmed cases have emerged in Korea, and thermal cameras are being installed in many places, including airports and hospitals. However, in many locations, staff must be stationed next to the cameras to monitor them constantly.]]></summary></entry><entry><title type="html">AWS 클라우드, 라즈베리파이, 온도센서를 활용한 체온 알람 서비스</title><link href="https://nalbam.github.io//2020/02/28/body-temperature-alarm-ko.html" rel="alternate" type="text/html" title="AWS 클라우드, 라즈베리파이, 온도센서를 활용한 체온 알람 서비스" /><published>2020-02-28T00:00:00+00:00</published><updated>2020-02-28T00:00:00+00:00</updated><id>https://nalbam.github.io//2020/02/28/body-temperature-alarm-ko</id><content type="html" xml:base="https://nalbam.github.io//2020/02/28/body-temperature-alarm-ko.html"><![CDATA[<p>요즘 신종 코로나19(COVID-19) 로 인하여 국내외가 어지럽습니다. 한국에서도 많은 수의 확진자가 발생하고 있으며, 공항, 병원 등을 비롯한 많은 곳에 열감지 카메라들이 설치되고 있습니다. 하지만 많은 곳에서 카메라 옆에 사람이 상주하여 카메라를 계속 모니터링 하고있습니다.</p>

<p>저는 IT 개발자로서 알고있는 지식으로 더 편하고, 빠르고, 정확하게 판단하고 알려주는 서비스를 만들고 싶다는 생각을 하게 되었습니다.</p>

<p><img src="/assets/images/2020-02-28/doorman.jpg" alt="doorman" /></p>

<h2 id="thermal-camera">Thermal camera</h2>

<p>더 화소수가 많고 성능이 좋은 카메라는 비싸고, 특히 중국에서 배송이 되야 하므로, 즉시 구할수 있는 8x8 의 해상도를 가진 열화상 카메라를 구매했습니다.</p>

<p>AMG8833 센서를 부착한 <a href="http://www.devicemart.co.kr/goods/view?no=12382843">Adafruit AMG8833 IR Thermal Camera Breakout</a> 입니다.</p>

<p><img src="/assets/images/2020-02-28/amg8833.jpg" alt="amg8833" /></p>

<p><a href="https://learn.adafruit.com/adafruit-amg8833-8x8-thermal-camera-sensor/raspberry-pi-thermal-camera">python + pygame 으로 만든 샘플 코드 입니다.</a></p>

<h2 id="raspberry-pi">Raspberry pi</h2>

<p>처음에는 <a href="https://aws.amazon.com/ko/deeplens/">AWS Deeplens</a> 가 고려되었으나, 열감지 카메라가 GPIO 를 통해 정보를 받으므로 다른 대안을 찾아야 했습니다.
그래서 책상 서랍에 있던 라즈베리파이를 선택 했습니다.</p>

<p>다행이도 라즈베리파이 케이스가 레고호환이어서 라즈베리 카메라와 열감지 카메라를 레고 거치대에 설치할 수 있었습니다.</p>

<p><img src="/assets/images/2020-02-28/raspberrypi.jpg" alt="raspberrypi" /></p>

<p>라즈베리파이에 python 으로 된 프로그램을 설치하고, <a href="https://aws.amazon.com/ko/s3/">Amazon S3</a> Bucket 에 사진을 업로드 할수 있는 권한도 부여 했습니다.</p>

<p>자세한 코드는 <a href="https://github.com/nalbam/rpi-doorman">여기</a>를 참고 하세요.</p>

<h2 id="slack-app">Slack App</h2>

<p>슬랙에 알림을 받거나, 슬랙에서 사용자 이름을 지정 하기 위하여 <a href="https://github.com/nalbam/deeplens-doorman/blob/master/README-slack.md">설정법</a> 에 따라 Slack App 을 만들어 줬습니다.</p>

<p><img src="/assets/images/2020-02-28/slack-04.png" alt="slack-04" /></p>

<h2 id="lambda-backend">Lambda Backend</h2>

<p>Amazon S3 Bucket 에 사진이 업로드 되면 <code class="language-plaintext highlighter-rouge">Trigger</code> 에 의하여 <a href="https://aws.amazon.com/ko/lambda/">AWS Lambda Function</a> 이 호출되야 합니다.
그리고 Lambda function 에서는 <a href="https://aws.amazon.com/ko/rekognition/">Amazon Rekognition</a> 으로 안면인식을 수행하여 사람별로 <a href="https://aws.amazon.com/ko/dynamodb/">Amazon DynamoDB</a> 에 저장 합니다.</p>

<p>이번에는 <a href="https://serverless.com/">Serverless framework</a> 을 이용하여 개발 및 배포를 했습니다.</p>

<p>자세한 코드는 <a href="https://github.com/nalbam/deeplens-doorman-backend">여기</a>를 참고 하세요.</p>

<h2 id="amplify-frontend">Amplify Frontend</h2>

<p>DynamoDB 에 저장된 이름과 사진을 웹을 통해 서비스 합니다.
이 앱은 <a href="https://aws.amazon.com/ko/amplify/">AWS Amplify</a> 를 이용하여 개발 및 배포를 했습니다.</p>

<p>Frontend 는 <code class="language-plaintext highlighter-rouge">Javascript</code> 와 <code class="language-plaintext highlighter-rouge">React</code> 를 사용 했습니다.
그리고 <code class="language-plaintext highlighter-rouge">Rest API</code> 를 사용하여 Backend 에서 만든 DynamoDB 를 조회 하였고, 이 역시 <code class="language-plaintext highlighter-rouge">AWS Lambda Function</code> 으로 생성 하였습니다.</p>

<p>인식은 하였으나 이름을 모르는 사람은 <code class="language-plaintext highlighter-rouge">Unknown</code> 으로 저장 하였고, 이름을 저장 하는 폼을 위해 <a href="https://aws.amazon.com/ko/cognito/">Amazon Cognito</a> 를 사용해서 인증을 처리 했습니다. Amplify 를 통해 손 쉽게 로그인 및 가입 페이지를 직접 코딩하지 않고도 적용할 수 있었습니다.</p>

<p><img src="/assets/images/2020-02-28/doorman-web.jpg" alt="doorman-web" /></p>

<p>자세한 코드는 <a href="https://github.com/nalbam/doorman">여기</a>를 참고 하세요.</p>

<h2 id="architecture">Architecture</h2>

<p><img src="/assets/images/2020-02-28/doorman-arch.jpg" alt="doorman-arch" /></p>

<p>감사합니다.</p>]]></content><author><name></name></author><category term="thermal-camera" /><category term="raspberry-pi" /><category term="covid-19" /><summary type="html"><![CDATA[요즘 신종 코로나19(COVID-19) 로 인하여 국내외가 어지럽습니다. 한국에서도 많은 수의 확진자가 발생하고 있으며, 공항, 병원 등을 비롯한 많은 곳에 열감지 카메라들이 설치되고 있습니다. 하지만 많은 곳에서 카메라 옆에 사람이 상주하여 카메라를 계속 모니터링 하고있습니다.]]></summary></entry></feed>