bub-folotoy is a Bub channel plugin for FoloToy devices.
It is based on folotoy-openclaw-plugin for the MQTT topics, credential flows, and message formats
- MQTT inbound message listening for toy speech
- Explicit Bub tools for spoken replies and proactive notifications
- Bundled
folotoyskill undersrc/skills/folotoy - Direct official-broker auth with
toy_snandtoy_key - API-based MQTT credential exchange
- Explicit MQTT credential override for self-hosted broker setups
- Immediate transitional acknowledgment with configurable
BUB_FOLOTOY_ACK_TEXT
- Python
>=3.12
From the local repository:
uv pip install git+https://github.com/bubbuild/bub-folotoy.gitSettings are loaded from environment variables through FoloToySettings with prefix BUB_FOLOTOY_.
Start from .env.example. A minimal setup for the official broker looks like this:
BUB_FOLOTOY_TOY_SN=your-toy-sn
BUB_FOLOTOY_TOY_KEY=your-toy-key
BUB_FOLOTOY_MQTT_HOST=f.folotoy.cn
BUB_FOLOTOY_MQTT_PORT=1883
BUB_FOLOTOY_ACK_TEXT="收到,稍等一下。"
BUB_MODEL=openrouter:openai/gpt-5.4-nano
BUB_OPENROUTER_API_KEY=your-openrouter-api-key
BUB_OPENROUTER_API_BASE=https://openrouter.ai/api/v1
BUB_ENABLED_CHANNELS=folotoyIf you use a self-hosted broker, set BUB_FOLOTOY_MQTT_USERNAME and BUB_FOLOTOY_MQTT_PASSWORD.
If you use API-based MQTT credential exchange, set BUB_FOLOTOY_FLOW=api, BUB_FOLOTOY_API_URL, and BUB_FOLOTOY_API_KEY.
Credential resolution order is:
- explicit MQTT username and password
- API flow when
BUB_FOLOTOY_FLOW=api - direct
toy_snandtoy_key
Defaults and resolution logic are implemented in src/bub_folotoy/folotoy.py.
The MQTT topics follow folotoy-openclaw-plugin:
Inbound /openapi/folotoy/{sn}/thing/command/call
Reply /openapi/folotoy/{sn}/thing/command/callAck
Notification /openapi/folotoy/{sn}/thing/event/post
Inbound payload:
{
"msgId": 1,
"identifier": "chat_input",
"inputParams": {
"text": "hello",
"recording_id": 100
}
}Reply payload:
{
"msgId": 1,
"identifier": "chat_output",
"outParams": {
"content": "hello",
"recording_id": 100,
"order": 1,
"is_finished": false
}
}Finish frame:
{
"msgId": 1,
"identifier": "chat_output",
"outParams": {
"content": "",
"recording_id": 100,
"order": 2,
"is_finished": true
}
}Notification payload:
{
"msgId": 1,
"identifier": "send_notification",
"outParams": {
"text": "Time to drink water."
}
}At runtime the plugin creates one FoloToyMessageListener, registers the Bub channel folotoy, and injects that listener through load_state().
- inbound MQTT speech becomes a Bub
ChannelMessage - if configured, the channel sends
BUB_FOLOTOY_ACK_TEXT - the LLM should answer by calling
folotoy.reply - proactive toy-side alerts should use
folotoy.notify - the plugin publishes one or more
chat_outputframes and a finalis_finished=trueframe
The provided skill at src/skills/folotoy/SKILL.md tells the model to use the correct tool for normal replies and one-way alerts.