Skip to main content

asf02

Lulumiao pet slow feeders can only be managed via an App that has since been delisted from the Google Play store. Without the App, owners cannot update feed schedules or synchronize the feeder clock which is prone to drift. This library allows you to once again manage your slow feeder and save it from the landfill.

Python client library for managing Lulumiao pet slow feeders over BLE. The protocol was reverse-engineered from the vendor Android app and verified to work with devices having v1.19.12 firmware.

The feeder speaks a small JSON-RPC dialect over two GATT characteristics. This library wraps the whole conversation — discovery, pairing, unlock, schedule management, on-demand feeding, and status — behind a tiny async API. Built on bleak; no other runtime dependencies.

Product Details

Brand Lulumiao
Device Pet slow feeder
Manufacturer Hong Kong M Spacer Limited
Model CWH01L-A1 (OEM model ASF02, per info and BLE advertising)
FCC ID 2A9QN-CWH01L-A1

Product Images

Lulumiao pet slow feeder

Install

From a local repo checkout

pip install -e ".[dev]"  # includes pytest

From GitHub

pip install git+https://github.com/cptskippy/asf02.git

From PyPI

pip install asf02

Quick start

import asyncio
from asf02 import ASF02Client

async def main():
    async with ASF02Client("<DEVICE_MAC>", token="<BIND_TOKEN>") as feeder:
        await feeder.unlock()                 # required on every connect
        await feeder.sync_time(-25200)        # PDT w/DST in seconds
        info = await feeder.info()            # {'f': '1.19.12', 'm': 'ASF02', ...}
        status = await feeder.status()        # {'n': 1065, 'b': 458, ...}
        await feeder.feed(1)                  # dispense 1 portion now
        await feeder.set_schedule([(15, 30, 1), (16, 0, 1)])

asyncio.run(main())

Discovery

from asf02 import discover_feeders

async def main():
    for f in await discover_feeders(timeout=10):
        print(f.address, f.model, f.rssi)

Feeders advertise continuously: local name ASF02-<last6 of MAC> plus manufacturer data (company 0xFFFF) starting 02 03, with the MAC embedded.

Pairing / tokens

  • Generate a token: ASF02Client.new_token() (10 chars, same charset as the vendor app).
  • Keep the token with your device config. The unlock code is always derived from it (md5("0000:"+token) — see unlock_code_for).
  • Store it in the device: await feeder.bind(token). Re-binding overwrites the previous token — the old one stops working.

Schedules

  • The device stores a fixed 10-slot array; set_schedule replaces it.
  • Entries are (hour, minute, portion) or (hour, minute, portion, enabled).
  • set_schedule([]) (or clear=True) wipes the schedule.
  • Time fields are hex on the wire — the library handles this, but know it: 0012000200 is 18:00, not 12:00.
  • The write response is the authoritative confirmation (some firmware builds answer schedule reads with stale/empty data).

Behavior notes (learned from the hardware)

  • The device locks across every (re)connect — call unlock() each time (or use connect_unlocked()). Only info works while locked.
  • time.calibration takes epoch seconds + tz offset in seconds (incl. DST; e.g. −25200 for PDT — same convention the vendor app uses). Always re-sync on connect; schedules run on the device clock.
  • feeder.log may return no notification at all — get_log() returns None in that case instead of erroring.
  • No MTU negotiation needed for any documented command (the link carries the full 79-byte responses fine).
  • Transient connections are fine: connect → work → disconnect. The feeders run autonomously between visits.
  • The feeder clock drifts over time and freezes entirely while unpowered. Sync it (sync_time()) semi-regularly — and on every reconnect if you want schedules to stay on time — since the schedule runs on the device clock, not yours.

API Status

Method Status
info ✅ Working
bind ✅ Working
unlock ✅ Working
time.calibration ✅ Working
feeder.status ✅ Working
feeder.feed ✅ Working
feeder.stop ✅ Working
feeder.plan (read) ⚠️ No result on v1.19.12 firmware
feeder.plan (write) ✅ Working
feeder.log ⚠️ No result on v1.19.12 firmware

IMPORTANT NOTE: While feeder.plan (read) and feeder.log are defined, the v1.19.12 firmware appears to have a bug and it will always return null. It is advised to retain a copy of any plan sent the feeder as you may not be able to retrieve it in the future.

Project layout

asf02/
  protocol.py    pure codecs + constants (unit-testable, no I/O)
  client.py      async device client (bleak)
  discovery.py   scanner
examples/cli.py  demo CLI (run on a machine with Bluetooth)
tests/           unit tests anchored to real captured wire traffic

Publishing

See CONTRIBUTING.md for how releases are cut and how PyPI publishing is configured.

License

Apache-2.0

Metadata

Release files for asf02 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for asf02 0.1.0
File Size Uploaded
asf02-0.1.0.tar.gz 22.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for asf02 0.1.0
File Interpreter ABI Platform
asf02-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 39.8 kB

Release files / asf02-0.1.0.tar.gz

Download URL asf02-0.1.0.tar.gz
Size 22.1 kB
Tags Source
SHA-256 checksum
How to use checksums
945a3be2f377cb041a93cdb290fbea0a1939c54beaa933e694c83a9fec4eb91a
BLAKE2b-256 checksum
How to use checksums
486cbdd5de1130ad4050fd4b968d291734032d7dfb38f5268e6409ae5a57b2ad
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.

Transparency log

Release files / asf02-0.1.0-py3-none-any.whl

Download URL asf02-0.1.0-py3-none-any.whl
Size 17.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f5d5869536b123e7fe3596b573afbdf90c02ed63edce9b6e26dc73f1c89911d8
BLAKE2b-256 checksum
How to use checksums
9e1b61375ef0a049476a2d93e246cd8fde1d63543df1dde5e628ebe85532ab19
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.1

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page