Tick + ATOM Matrix & Atomic Echo Base

THREE-KNOCK

Same ATOM Matrix as the other devices — plugged into an Atomic Echo Base for a real speaker and mic. Play a knock sound on a schedule. 3:07 AM. Every night.

⚠ ADULT SUPERVISION REQUIRED ⚠

These instructions involve electronics and tools that may cause injury. Adult supervision is required. Use at your own risk.

See full Safety & Legal Notice and Terms of Use.

ATOM Matrix plugged into Atomic Echo Base

ATOM Matrix (~$15) + Atomic Echo Base (~$6). Plug together. No wires.

What’s On the Board

Same ATOM Matrix as every Tick device. For Three-Knock, it plugs into the Atomic Echo Base — a speaker and microphone board. Two boards, stacked. One device.

Atomic Echo Base
Speaker1W cavity speaker with Class-D amp (NS4150B)
MicrophoneMEMS mic, 65 dB SNR
Audio CodecES8311 — 24-bit, I2S, 16–64 kHz
AudioFull-duplex (play + record at the same time)
ATOM Matrix (plugs in on top)
ChipESP32-PICO-D4, dual-core 240 MHz, 4MB flash
LEDs5×5 WS2812C RGB matrix — flash amber on each knock
MotionMPU6886 accelerometer + gyroscope
WirelessWi-Fi (built in)
PowerUSB-C, 5V
FirmwareCircuitPython + Tick — get from GitHub
Price~$21 combined

Same ATOM Matrix used for Swamp Glow and Route 13. The Echo Base adds audio for Three-Knock.

Step 1 — Load the Firmware

Download the firmware zip and extract it on your computer.

Plug the ATOM Matrix into the Atomic Echo Base. Connect USB-C to your computer. It shows up as a drive called CIRCUITPY. Copy the three-knock folder contents onto the drive. The device reboots automatically and starts running. It boots in about 2 seconds.

What happens on boot:

ATOM Matrix + Atomic Echo Base assembled

Two boards stacked. Small enough to hide behind a door frame.

Step 2 — AP Setup Page

The device becomes a hotspot. Configure from a web page on your phone.

Hold the button for 3 seconds. The LED grid turns amber — AP mode.

1

Connect to the hotspot

Wi-Fi name: Tick   Password: 11111111

2

Open 192.168.4.1

A setup page loads from the device. Upload audio, set the schedule, adjust volume — all from a web form. Plus you can set Wi-Fi credentials for Step 3.

3

Save and reboot

Hit save. The device reboots with your settings. Schedule is active. Audio is stored in flash.

The setup page is embedded in the firmware — a single HTML file served by the ESP32’s web server. No internet required. Dark background, amber accents, cabin-in-the-woods theme. It looks like it belongs to the book.

Preview the device page →

Step 3 — Wi-Fi

Optional. Connect to a network for synchronized playback across multiple devices.

On the AP setup page, enter your Wi-Fi network name and password. Save. The device reboots and joins the network.

Now it can sync time with NTP. The schedule becomes precise — not “roughly 3:07 AM” but exactly 3:07:00 AM.

With multiple devices on the same network, they all play at the same second. That’s the part that matters — synchronized knocking.

You can also control the device from any browser on the network — same web page, same controls. Just go to the device’s IP address.

Under the Hood

What’s running on the device. CircuitPython.

The ATOM Matrix runs CircuitPython with the Tick library. All the code lives on the CIRCUITPY drive. Here’s what it does:

I2S Audio Init

The Atomic Echo Base connects over I2S. The ES8311 codec handles digital-to-analog conversion:

import board
import audiobusio
import audiocore

# I2S pins for the Atomic Echo Base
i2s = audiobusio.I2SOut(
    bit_clock=board.G19,   # BCK
    word_select=board.G33, # WS / LRCK
    data=board.G22         # data out to speaker
)

Playing the Knock

The knock audio is a WAV file stored on the device’s flash:

def play_knock():
    wav = audiocore.WaveFile(open("/knock.wav", "rb"))
    i2s.play(wav)

    # Wait for playback to finish
    while i2s.playing:
        pass

LED Flash

The 5×5 grid flashes amber with each knock:

import neopixel
import time

NUM_LEDS = 25
np = neopixel.NeoPixel(board.G27, NUM_LEDS, auto_write=False)

def flash_amber():
    np.fill((40, 25, 0))   # warm amber
    np.show()
    time.sleep(0.05)
    np.fill((0, 0, 0))      # dark
    np.show()

def knock_with_flash():
    for i in range(3):
        flash_amber()
        play_knock()
        time.sleep(0.6)

Knock Detection (Microphone)

The MEMS mic listens for someone knocking back:

import array
import audiobusio

mic = audiobusio.PDMIn(
    clock_pin=board.G19,
    data_pin=board.G23,    # data in from mic
    sample_rate=16000,
    bit_depth=16
)

samples = array.array("H", [0] * 256)

def detect_knock():
    mic.record(samples, len(samples))
    peak = max(samples)

    # Loud spike = someone knocked back
    return peak > 8000  # tune for your door

Schedule with NTP

With Wi-Fi, the device syncs real time and plays at exactly 3:07 AM:

import wifi
import socketpool
import adafruit_ntp
import time

# Connect to Wi-Fi and sync time
wifi.radio.connect(ssid, password)
pool = socketpool.SocketPool(wifi.radio)
ntp = adafruit_ntp.NTP(pool, tz_offset=-5)

played_today = False

while True:
    now = ntp.datetime

    if now.tm_hour == 3 and now.tm_min == 7 and not played_today:
        knock_with_flash()
        played_today = True

    # Reset at midnight
    if now.tm_hour == 0 and now.tm_min == 0:
        played_today = False

    time.sleep(1)

AP Setup Page

When you hold the button, the device starts a web server:

import wifi
import socketpool
from adafruit_httpserver import Server, Request, Response

# Start AP mode
wifi.radio.start_ap(ssid="Tick", password="11111111")

pool = socketpool.SocketPool(wifi.radio)
server = Server(pool, "/static")

@server.route("/")
def index(request: Request):
    return Response(request, body=SETUP_HTML,
                    content_type="text/html")

@server.route("/upload", methods=["POST"])
def upload_audio(request: Request):
    with open("/knock.wav", "wb") as f:
        f.write(request.body)
    return Response(request, body="Uploaded.")

@server.route("/save", methods=["POST"])
def save(request: Request):
    save_settings(request.form_data)
    return Response(request, body="Saved. Rebooting...")

server.serve_forever(str(wifi.radio.ipv4_address_ap))

Mount It

No housing needed. The stacked boards are tiny enough to hide as-is:

The knock should sound like it comes from the door itself. If it sounds electronic, move the speaker closer to the wood.

Mounted behind a door frame

Update the Firmware

Plug in USB. Drag and drop. Done.

The device runs CircuitPython. When you plug it into a computer, it shows up as a USB drive called CIRCUITPY. The Tick code is a set of .py files on that drive. To update:

1

Get the latest code

Download the latest files from the i4Seer GitHub repo:

github.com/i4seer/tick-three-knock

Click the green Code button, then Download ZIP. Or use git clone if you know git.

2

Plug in the device

Connect via USB-C. A drive called CIRCUITPY appears on your computer — just like a thumb drive.

3

Copy the new files

Drag the updated .py files from the download onto the CIRCUITPY drive. Replace the old files.

CIRCUITPY/
  code.py          ← main program
  tick_audio.py    ← I2S playback + mic
  tick_led.py      ← LED flash patterns
  tick_web.py      ← AP setup page
  tick_schedule.py ← NTP + timing
  knock.wav        ← default knock sound
  settings.toml    ← saved settings
4

Done

The device reboots automatically when you save files. Your settings (schedule, volume) are preserved. Unplug USB and it runs on its own.

Updates are optional. The device works out of the box. But if i4Seer releases new features, bug fixes, or better knock sounds — this is how you get them. No special tools. Just copy files.

All 3 Build Versions Read Book 3 About Tick