MemoTalk S1 / AI-PEN voice recorders โ fully unlocked for Python.
22 raw hardware commands distilled into 6 typed capability groups.
Scan, connect, record, stream live audio, download โ in ~10 lines.
- Zero-dependency protocol layer.
bleakis only needed when talking to real hardware. - Every reply is a
dataclass. No more hand-rollingpkt[3:-2]slices โ batteries, file lists and settings arrive typed and validated. - Built-in device simulator. Develop and test the full stack with no pen in hand.
- 154 unit tests, byte-level asserts from real captures. The parser suite is literally a replay of genuine device traffic.
- Live audio & level streaming. Real-time dB meter or 72 ms MP3 chunks while recording โ ready for streaming ASR.
- Wi-Fi fast transfer. One context manager opens the hotspot, reads FTP credentials and hands you a live FTP session.
pip install memotalk-sdkDevelopment install:
pip install -e ".[dev]"Dependency:
bleak >= 0.21โ only when connecting to a real device.
import asyncio
from memotalk import MemoTalkClient
async def main():
async with MemoTalkClient() as dev: # scan + connect + time sync + keepalive
print(await dev.battery()) # BatteryInfo(level=5, percent=50)
for entry in await dev.files.list():
print(entry.name, entry.size, entry.duration_sec)
path = await dev.files.download((await dev.files.latest()).name, "./downloads")
print("saved", path)
asyncio.run(main())async with MemoTalkClient() as dev:
await dev.record.set_realtime(False) # 0xA4=0 -> pushes 0xA8 levels
await dev.record.start()
async for level in dev.stream.level():
print(f"{level.db:6.1f} dB {'#' * int(level.normalized * 30)}")
await dev.record.stop()async with MemoTalkClient() as dev:
await dev.record.set_realtime(True) # 0xA4=1 -> pushes 0x90 MP3
await dev.record.start()
async for chunk in dev.stream.audio(): # 288 bytes = 2 MP3 frames = 72 ms
feed_asr(chunk) # straight into streaming ASR
await dev.record.stop()
await dev.record.set_realtime(False)async with MemoTalkClient() as dev:
async with dev.wifi.session() as net: # 0x87 on -> read creds -> 0x87 off
print(net.ssid, net.password)
print(net.ftp_config.address)
net.join_hotspot() # auto-join on macOS
ftp = net.open_ftp() # PASV login
for name in net.list_audio_files(ftp):
net.download(ftp, name, "./downloads")
ftp.quit()The hotspot is always closed on exit โ even on exceptions.
async with MemoTalkClient() as dev:
print(await dev.settings.read_all()) # DeviceSettings(...)
await dev.settings.sleep_timeout(600) # 15~7200, or 0 = default
await dev.settings.max_record_minutes(120) # 1~480
await dev.settings.usb_mass_storage(True)frame = build_frame(0x77) # any protocol code
reply = await dev.raw.request(frame, code=0x77, timeout=2.0)| Group | Methods |
|---|---|
| Status | battery() firmware() identity() free_space() has_import_file() info() sync_time() |
| Record | record.start() pause() resume() stop() state() set_realtime() |
| Stream | stream.level() stream.audio() stream.states() stream.packets() |
| Files | files.list() latest() download() download_all() delete() clear_all() sync_control() |
| Settings | settings.read() read_all() write() sleep_timeout() max_record_minutes() usb_mass_storage() |
| Network | wifi.enable() disable() credentials() ftp() session() |
| Raw | raw.send() request() expect() |
Full hardware โ SDK mapping: CAPABILITY-MATRIX.md
memotalk scan
memotalk status --files
memotalk list
memotalk download --all --out ./downloads
memotalk record --seconds 5 # live level meter
memotalk record --seconds 5 --realtime # count received MP3 bytes
memotalk wifi --ls --download FILE.mp3
memotalk config --sleep 600 --usb 1
memotalk raw --hex 5A0301C080 --expect 01
# no hardware? use the simulator
memotalk --simulate statusmemotalk/
โโโ codec.py CRC16 / 0x5A framing / length inference / BCD / XOR-FF
โโโ commands.py 22 pure command builders + channel routing table
โโโ parsers.py every reply -> typed model (incl. 0x00 error frames)
โโโ assemblers.py frame reassembler / 0x13 listing / 0x84 audio stream
โโโ transport.py connect / write / request-reply correlation / keepalive
โโโ client.py the 6 capability groups
โโโ discovery.py scanning + macOS already-connected recovery
โโโ fake.py device simulator
โโโ cli.py command line
Length inference. The device uses three frame shapes (standard 2+len, 0x90's 3-byte length โ 295, 0x84's fixed 328). codec.expected_frame_length() derives the expected byte count from any prefix, so FrameReassembler stitches frames automatically โ even on low-MTU platforms.
Request-reply correlation. Predicate subscriptions are registered before writing, so fast replies are never missed. 0x00 error frames are matched too and surface as typed exceptions.
Loss-resilient transfer. On 0x84 timeouts, missing_sequences() pinpoints the first missing packet and the transfer resumes from it โ automatically, with bounded retries.
# uv (recommended; pytest lives in the dev extra)
uv run --extra dev pytest -q
# or stdlib
python -m unittest discover -s tests -v| File | Covers |
|---|---|
test_codec.py |
CRC, framing, BCD, byte-level asserts for all 22 commands, range validation |
test_commands.py |
builders โ sendable codes 1:1, write-channel routing, version consistency |
test_parsers.py |
24 real device captures โ typed models |
test_assemblers.py |
split / out-of-order / lost packets / CRC errors / padding truncation |
test_client.py |
simulator-driven end-to-end flows |
Every hex literal in test_parsers.py comes from real device captures.
| Doc | Contents |
|---|---|
| CAPABILITY-MATRIX.md | Hardware atomic capability โ SDK capability, line by line |
| CHANGELOG.md | Release history |
| docs/CODE-STRUCTURE.md | Layered structure & porting notes |
| docs/ERRATA.md | Protocol doc quirks & real-hardware findings |
| docs/HARDWARE-REGRESSION.md | Hardware regression details & raw JSON |
| docs/RELEASING.md | PyPI release process |
| REC02 cost breakdown | Per-unit hardware, manufacturing, development, service and fulfillment costs, with English and Chinese infographics |
The original SKILL stays put as a protocol dictionary + reference scripts; this package is the installable client built on top of it. cmd_* names were dropped โ cmd_read_battery() is now read_battery(), so migrations are just a prefix away.
If memotalk-sdk saves you an afternoon of BLE reverse-engineering, give it a star โ it helps others find it.
MIT โ Floatboat & MemoTalk. See LICENSE.
