Skip to content

About

MemoTalk S1 / AI-PEN BLE recorder SDK - typed protocol core, transport and capability API. Python today; JS/TS/Kotlin planned.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

ย 

History

33 Commits

Folders and files

Repository files navigation

๐ŸŽ™๏ธ memotalk-sdk

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.

CI PyPI Python License: MIT Tests

English ยท ็ฎ€ไฝ“ไธญๆ–‡


Highlights

  • Zero-dependency protocol layer. bleak is only needed when talking to real hardware.
  • Every reply is a dataclass. No more hand-rolling pkt[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.

Installation

pip install memotalk-sdk

Development install:

pip install -e ".[dev]"

Dependency: bleak >= 0.21 โ€” only when connecting to a real device.


Quick Start

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())

Record + live level meter

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()

Record + live transcription

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)

Wi-Fi fast transfer

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.

Settings

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)

Escape hatch

frame = build_frame(0x77)                        # any protocol code
reply = await dev.raw.request(frame, code=0x77, timeout=2.0)

API Map

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


CLI

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 status

Architecture

memotalk/
โ”œโ”€โ”€ 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.


Testing

# 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.


REC02 cost breakdown

Memotalk REC02 per-unit cost breakdown

View the full cost breakdown


Documentation

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

Migrating from memotalk-ble-skill

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.


Show your support

If memotalk-sdk saves you an afternoon of BLE reverse-engineering, give it a star โ€” it helps others find it.


License

MIT โ€” Floatboat & MemoTalk. See LICENSE.

About

MemoTalk S1 / AI-PEN BLE recorder SDK - typed protocol core, transport and capability API. Python today; JS/TS/Kotlin planned.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages