Tick + ATOM Matrix & Atomic Echo Base
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.
ATOM Matrix (~$15) + Atomic Echo Base (~$6). Plug together. No wires.
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 | |
| Speaker | 1W cavity speaker with Class-D amp (NS4150B) |
| Microphone | MEMS mic, 65 dB SNR |
| Audio Codec | ES8311 — 24-bit, I2S, 16–64 kHz |
| Audio | Full-duplex (play + record at the same time) |
| ATOM Matrix (plugs in on top) | |
| Chip | ESP32-PICO-D4, dual-core 240 MHz, 4MB flash |
| LEDs | 5×5 WS2812C RGB matrix — flash amber on each knock |
| Motion | MPU6886 accelerometer + gyroscope |
| Wireless | Wi-Fi (built in) |
| Power | USB-C, 5V |
| Firmware | CircuitPython + 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.
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:
Two boards stacked. Small enough to hide behind a door frame.
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.
Wi-Fi name: Tick Password: 11111111
192.168.4.1A 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.
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.
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.
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:
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
)
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
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)
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
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)
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))
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.
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:
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.
Connect via USB-C. A drive called CIRCUITPY appears on your computer — just like a thumb drive.
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
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.