跳到正文
DEV Community· tushar shah·· 3 小時前AI 評分50

Nishi Daak:搭載本地 Gemma 的 Arduino UNO Q 深夜呼喚回家者兩次

Nishi Daak: an Arduino UNO Q that calls your name twice when you come home late (local Gemma, no cloud)

AI 導讀

Nishi Daak 是一個以 Arduino UNO Q 和本地 Gemma 製作的裝置,會在家人深夜手機重新連上家中 Wi-Fi 時呼喚其名字兩次。裝置透過 llama.cpp 執行 Gemma 3 1B,生成速度為 4.7 至 4.9 tokens/s,並會寫下夜間故事。整個系統無需雲端、帳戶或 API keys,資料不會離開家中。

正文

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine

What I Built

In Bengal they say that late at night, someone may call your name from outside in the voice of a person you love. Do not answer. Not the first time, not the second. The Nishi (নিশি, "the night one") only ever calls twice, and whoever answers follows it into the dark and is never seen again. A real person, the story goes, will always call a third time.

Nishi Daak ("the night call") puts that spirit on a shelf. It is an Arduino UNO Q that:

  • Watches the door, without a camera. It notices when a family member's phone rejoins the home Wi-Fi late at night after being away for a while.
  • Calls their name, twice. The 8×13 LED matrix spells the name letter by letter, like a planchette, then spells it again. Never a third time.
  • Writes what followed them home. Gemma, running on the board itself through llama.cpp, writes a short eerie (but harmless) tale and saves it to a night log.
  • Holds séances. Open the board's web page from any phone on the home Wi-Fi, ask the Nishi a question, and it answers in character while a short version is spelled on the matrix.
  • Stays awake all day as two glowing eyes. They blink, and their brightness ("spirit energy") follows how many Bluetooth devices are nearby.

No cloud, no accounts, no API keys. Nothing leaves the house.

Demo

The LED matrix spelling TUSHAR, then TUSHAR again

The night call: the name, letter by letter, twice. Never a third time.

🎬 Watch the 74-second demo video (Google Drive)

The human scenes in the video are free stock footage from Pexels (a Kolkata evening, a walk home, a door at night). The board's behaviour, the timings and the numbers come from the real build. The tale typed out in the video is the few-shot example from the prompt; real ones from the board's night log are below.

This is the board's own web page, screenshotted live from the UNO Q on my home Wi-Fi. The eyes and the "2" show the spirit energy (5 Bluetooth devices nearby), the night log holds real tales Gemma wrote on the board, and the footer is the board's health check: presence ok, Gemma 3 1B ok, last reply 15 s at 4.7 tokens/s.

The Nishi Daak web page served by the UNO Q: glowing eyes, séance box, night log with three tales, health line

Three real entries from night_log.json, written by Gemma 3 1B on the board:

A single, crimson leaf drifted down the path, landing softly beside his feet. It mirrored a forgotten melody, a scent of rain and old roses. The Nishi smiles, a fleeting glimmer in the gloom.
(10.0 s, 4.7 tokens/s)

A single jasmine flower, bruised and drooping, drifted past the doorway. The rain smelled of dust and regret, a faint, silver light reflecting on it. It paused, a single tear falling: a beautiful sorrow, like a forgotten song.
(15.0 s, 4.7 tokens/s)

A single, pale moonbeam traced a path down the stairs. It watched a small, worn book open. A faded map, perhaps? The Nishi smiles, a fleeting shadow.
(12.6 s, 4.7 tokens/s)

Each log entry records the time the board caught the phone. Tap the small red icon next to a name (a flask for a test call, Wi-Fi for a real one) and a card shows how it was caught, how long the person was away, and how fast Gemma wrote the tale.

Code

Nishi Daak

It calls your name twice. Never answer.

An Arduino UNO Q that lives on a shelf and behaves like a Nishi, the night spirit of Bengali folklore. Built for the DEV Hacktoberfest Open-Source AI Challenge, Week 4 ("Ghost in the Machine"), Arduino category.

The folklore

In Bengal they say the Nishi comes late at night and calls your name in the voice of someone you love. It calls only twice. If you answer, you follow it into the dark and are never seen again. So if you hear your name at night, wait for the third call: a real person will always call a third time.

What it does

  • Idle: two glowing eyes on the board's 8x13 LED matrix. They blink and flicker, and their brightness ("spirit energy") follows how many Bluetooth devices are nearby. With no energy, only a faint stray light flickers now and then.
  • Night…
Part File Runs on
Matrix eyes and planchette sketch/sketch.ino the UNO Q's STM32 microcontroller
App logic and web API python/main.py App Lab container on the Linux side
Web page (séance, night log) assets/index.html App Lab web_ui brick, port 7000
Presence scanner host/presence_service.py Debian host, systemd
The spirit's voice llama.cpp llama-server + gemma-3-1b-it Q4_K_M Debian host, systemd

How I Built It

Wi-Fi presence, then the LED matrix, then local Gemma, all on one Arduino UNO Q

The UNO Q is two computers on one board: an STM32 microcontroller that drives the LED matrix, and a quad-core Linux side (4 GB of RAM on mine) running Debian. App Lab connects them with a Bridge, so Python on the Linux side can call Bridge.call("spell", "TUSHAR TUSHAR") and the sketch fades the letters in and out on the matrix.

Gemma on the board

llama.cpp builds on the board itself (47 minutes with -j2; 3.6 GB of RAM was enough). llama-server runs as a systemd service with Gemma 3 1B instruct, Q4_K_M (a 769 MB file):

What Measured on the board
Generation speed 4.7 to 4.9 tokens/s, 4 threads
One séance answer 8.5 to 9.1 s
A night tale 10 to 15 s
First reply after a restart 38.8 s (it processes the persona once)

A 1B model does not follow rules well, but it copies examples beautifully. So instead of a long list of "never do X", the prompt is a short persona plus two few-shot turns: one night tale and one dodged request for advice. A small tidy() pass then strips markdown, emojis and any "as an AI" talk, and keeps at most three sentences, so whatever Gemma says always fits the spirit. If Gemma is down, the Nishi falls back to a canned line, so the call never fails.

Catching a phone without a camera

A silhouette at the front door, a "Joined Home" Wi-Fi notification, and the board's log line: phone seen, away 3 h 12 min, it is night, call TUSHAR

From the demo video: the moment the phone rejoins the home Wi-Fi and the board decides to call. The footage is stock and the times are illustrative; the decision logic is the real code below.

The Nishi needs to know who came home, but I did not want cameras or an app on anyone's phone. Phones randomize their Bluetooth addresses constantly, so Bluetooth only feeds the "energy" count. Wi-Fi is different: Android and iOS keep one stable private MAC per network. A small root service on the host runs arp-scan on the home network and serves the result as JSON, and the app turns it into arrivals:

def arrivals(macs, now):
    """(name, seconds away) for known people whose phone reappeared after being away."""
    back = []
    for p in CFG["people"]:
        mac = p["mac"].lower()
        if mac not in macs:
            continue
        prev = last_seen.get(mac)
        if prev is not None and now - prev > away_seconds():
            back.append((p["name"], now - prev))
        last_seen[mac] = now  # first sighting after app start never counts as an arrival
    return back

Then reality got in the way, twice.

Locked phones vanish. A locked phone in Wi-Fi power save answers ARP after up to 2 seconds, and the default arp-scan gives up after 500 ms. With --retry=4 --timeout=1500 it went from 4 of 6 scans to 6 of 6. But during a 30-minute test with the phone locked on the table, it was seen once in 62 polls: a sleeping phone drops off Wi-Fi entirely, and no scan can see a disconnected phone. So the "away" threshold is 3 hours, not 20 minutes. Otherwise the Nishi would call my name every time my phone took a nap next to me.

The board kept dying after about 15 hours. Ping worked, SSH refused, and only a power cycle brought it back. The journal of the dead boot had the answer: The maximum number of active connections for UID 0 has been reached (max_connections_per_user=256). Each BLE scan used asyncio.run(), a fresh event loop every time, and bleak opened a new system D-Bus connection for each loop without closing the old one. The connections piled up until root hit the limit, and from then on nothing running as root could talk to D-Bus. The fix is one event loop for every scan:

BLE_LOOP = asyncio.new_event_loop()  # one loop for every scan: bleak keeps a D-Bus connection per loop

def scan_ble():
    found = BLE_LOOP.run_until_complete(BleakScanner.discover(timeout=BLE_SECONDS, return_adv=True))

Before the fix, the service's socket count grew by one with every scan. After it, the count stayed flat at 6.

Why Does Open Innovation Matter?

This project only makes sense with an open model running locally:

  • The data is my family's comings and goings. The core signal is which phone came home, and when. That is exactly the kind of data I do not want on someone else's server. With Gemma running on the board, the phone addresses, the timestamps and every tale stay in a JSON file on a shelf in my house.
  • It works with the internet off. Ghost stories are for power cuts and bad nights. The Nishi only needs the home Wi-Fi router; unplug the internet and it still calls your name and still tells you what followed you home.
  • It costs nothing to run. No per-request bill for a spirit that might speak a dozen times a night, and no API key to leak.
  • The spirit is mine to change. The persona is a few lines of text and two examples. I can swap the model for a smaller Gemma on a 2 GB board or a bigger one somewhere else, change the voice, or change the language in config.json, and nothing depends on a hosted model's mood that week.
  • It lasts. Folklore survives because it is passed down, not hosted. A Nishi that depends on a service that might shut down someday is a very short-lived ghost.

My Agent Session

I built this with Claude Code as a pair, running the board over SSH from my laptop. The session below is a curated slice of the real build: the board setup, the locked-phone tests, the crash that turned out to be a D-Bus leak, and the night log's details card.

The Spirit of It

The spookiest moment wasn't a ghost story. It was the board going silent after 15 hours: it still answered a ping, but nothing else. Something was still home, but it wouldn't come to the door. It turned out to be 256 D-Bus connections that never closed, which is a very modern kind of haunting.

It is an old story, living in a small board, made out of open pieces anyone can read and change.

It calls your name twice. Never answer.

Prize Categories

  • Best Use of Arduino: the whole project is an Arduino UNO Q acting as a physical agent that senses (Wi-Fi presence, Bluetooth energy) and acts (the LED matrix planchette), with the language model running on the board itself.
  • Best Use of Gemma: Gemma 3 1B instruct (Q4_K_M) runs locally on the UNO Q through llama.cpp and writes every night tale and séance answer, at 4.7 tokens/s, with no cloud.

來源:DEV Community · dev.to