Neural TTS for Sonoff iHost & smart home with MaryTTS and Node-RED.
2.2K
Give your Sonoff iHost and smart home a natural, human-like voice. Clean TTS brings lifelike neural text-to-speech directly to the iHost's built-in speaker and to any smart media player on your local network.
http://ihost.local:8123 to test voices, adjust defaults, and test speech right from your browser.tmalex/clean-ttslatest image.host (do NOT use bridge mode; bridge causes connection errors with the internal speaker API). In host mode, the port is fixed to 8123.clean-tts) and map it to /data. Do NOT mount it to another container's directory (like Node-RED). This dedicated folder keeps your custom words, generated voices, and settings permanently safe across container updates.http://ihost.local:8123In Clean TTS, you can configure your favorite default voice, volume, and speed in the web dashboard.
Whenever you send an announcement from Node-RED, Home Assistant, or an HTTP request, you can simply send the text, or you can optionally override any setting for that specific announcement:
| Parameter | Type | Example | What it does |
|---|---|---|---|
text | string | "Front door opened" | Required. The message to speak. |
lang | string | "en", "ro", "de", "fr", "ca" | Language code. Automatically picks the best native voice. |
voice | string | "en-US-JennyNeural" | Select a specific male or female voice. |
volume | string | "+20%", "+0%" | Audio Generation Volume (MP3/WAV): Software loudness level encoded directly into the synthesized audio file via SSML/FFmpeg for all devices and streams. (Not hardware speaker level). |
rate | string | "+10%", "-10%" | Speech speed (faster or slower). |
pitch | string | "+5Hz", "-5Hz" | Voice pitch adjustment. |
bell | boolean | true / false | Enable or disable the notification chime before speaking. |
nocache | boolean | true / false | Set true for spontaneous one-off alerts (current time, live sensor readings) to skip disk cache. |
💡 Tip: Any parameter you omit automatically falls back to your saved settings from the web dashboard!
⚡ Caches vs. No-Cache: Fixed alerts ("Front door opened") are cached on disk for instant (0ms) replay. Spontaneous alerts ("It is 12:45 PM, 21.5°C") can be sent with
"nocache": trueso 0 bytes are wasted on your storage drive. You can also run the container with-e NOCACHE=trueto enforce streaming mode by default.
volume: "+20%"): Regulates the sound loudness of the generated MP3 file itself. Use this in your message payloads to make individual announcements louder or quieter without triggering any hardware side-effects.POST /api/ihost/volume or Web UI Slider): Controls the actual physical speaker amplifier on Sonoff iHost [0-100].
⚠️ Important: Whenever iHost receives a hardware volume change command, the iHost firmware automatically plays an unskippable native feedback chime/jingle on the built-in speaker. Because this feedback sound temporarily occupies the speaker channel, always send hardware volume changes as a standalone command (e.g. for Day/Night schedules) and maintain an interval of at least 4–5 seconds before or after any TTS announcement.
http://ihost.local:8123 (automatically saved).// Function node connected to an HTTP Request node (POST http://127.0.0.1:8123/api/ihost/volume)
msg.url = "http://127.0.0.1:8123/api/ihost/volume";
msg.method = "POST";
msg.headers = { "Content-Type": "application/json" };
msg.payload = { "volume": 100 }; // Set 100 for Daytime / 30 for Nighttime
return msg;
http://ihost.local:8123/help[
{
"id": "subflow_clean_tts",
"type": "subflow",
"name": "Clean TTS Speaker",
"info": "Studio-grade neural voice announcement service for smart home and speaker notifications (Clean TTS).",
"category": "output",
"in": [{"x": 60, "y": 80, "wires": [{"id": "sf_func_1"}]}],
"out": [{"x": 580, "y": 80, "wires": [{"id": "sf_http_1", "port": 0}]}],
"env": [
{
"name": "DEFAULT_TEXT",
"type": "str",
"value": "Hello! This is a voice test from Clean TTS.",
"ui": {
"label": {"en": "Default Announcement Text"},
"type": "input",
"opts": {"types": ["str"]}
}
}
],
"meta": {
"module": "clean-tts-ihost",
"type": "subflow"
},
"color": "#38bdf8",
"icon": "font-awesome/fa-bullhorn"
},
{
"id": "sf_func_1",
"type": "function",
"z": "subflow_clean_tts",
"name": "Format Payload & Set API URL",
"func": "const defaultText = env.get(\"DEFAULT_TEXT\") || \"Hello! This is a voice test from Clean TTS.\";\n\nlet text = \"\";\nlet bell = true;\nlet volume = undefined;\nlet rate = undefined;\nlet pitch = undefined;\nlet nocache = undefined;\n\nif (typeof msg.payload === \"string\" && msg.payload.trim().length > 0) {\n text = msg.payload.trim();\n} else if (typeof msg.payload === \"object\" && msg.payload !== null) {\n text = msg.payload.text || msg.payload.say || defaultText;\n if (msg.payload.bell !== undefined) bell = msg.payload.bell;\n if (msg.payload.volume !== undefined) volume = msg.payload.volume;\n if (msg.payload.rate !== undefined) rate = msg.payload.rate;\n if (msg.payload.pitch !== undefined) pitch = msg.payload.pitch;\n if (msg.payload.nocache !== undefined) nocache = msg.payload.nocache;\n} else {\n text = defaultText;\n}\n\nmsg.url = \"http://127.0.0.1:8123/api/tts\";\nmsg.method = \"POST\";\nmsg.headers = {\n \"Content-Type\": \"application/json\"\n};\n\nconst payloadObj = {\n \"text\": text,\n \"bell\": bell\n};\nif (volume !== undefined) payloadObj.volume = volume;\nif (rate !== undefined) payloadObj.rate = rate;\nif (pitch !== undefined) payloadObj.pitch = pitch;\nif (nocache !== undefined) payloadObj.nocache = nocache;\n\nmsg.payload = payloadObj;\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 240,
"y": 80,
"wires": [["sf_http_1"]]
},
{
"id": "sf_http_1",
"type": "http request",
"z": "subflow_clean_tts",
"name": "Clean TTS API",
"method": "use",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 450,
"y": 80,
"wires": [[]]
},
{
"id": "tab_test_clean_tts",
"type": "tab",
"label": "Clean TTS Test",
"disabled": false,
"info": "Clean TTS announcement and hardware speaker test flow",
"env": []
},
{
"id": "inject_tts_test_simple",
"type": "inject",
"z": "tab_test_clean_tts",
"name": "🔊 Simple Voice Test",
"props": [{"p": "payload"}],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "Hello! This is a voice announcement test from Node-RED.",
"payloadType": "str",
"x": 190,
"y": 120,
"wires": [["test_tts_node"]]
},
{
"id": "inject_tts_test_advanced",
"type": "inject",
"z": "tab_test_clean_tts",
"name": "🚨 High Volume Alert (+30%)",
"props": [{"p": "payload"}],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"text\": \"Warning! Motion detected in the backyard garden.\", \"bell\": true, \"volume\": \"+30%\", \"rate\": \"+10%\"}",
"payloadType": "json",
"x": 210,
"y": 180,
"wires": [["test_tts_node"]]
},
{
"id": "inject_tts_test_night_vol",
"type": "inject",
"z": "tab_test_clean_tts",
"name": "🌙 Set Night Volume (30%)",
"props": [{"p": "payload"}],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"volume\": 30}",
"payloadType": "json",
"x": 200,
"y": 280,
"wires": [["hw_vol_http_node"]]
},
{
"id": "inject_tts_test_day_vol",
"type": "inject",
"z": "tab_test_clean_tts",
"name": "☀️ Set Day Volume (100%)",
"props": [{"p": "payload"}],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"volume\": 100}",
"payloadType": "json",
"x": 190,
"y": 340,
"wires": [["hw_vol_http_node"]]
},
{
"id": "test_tts_node",
"type": "subflow:subflow_clean_tts",
"z": "tab_test_clean_tts",
"name": "Clean TTS Speaker",
"env": [{"name": "DEFAULT_TEXT", "value": "Hello from Clean TTS", "type": "str"}],
"x": 520,
"y": 150,
"wires": [["test_tts_debug"]]
},
{
"id": "hw_vol_http_node",
"type": "http request",
"z": "tab_test_clean_tts",
"name": "POST /api/ihost/volume",
"method": "POST",
"ret": "obj",
"paytoqs": "ignore",
"url": "http://127.0.0.1:8123/api/ihost/volume",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [{"keyType": "other", "keyValue": "Content-Type", "valueType": "other", "valueValue": "application/json"}],
"x": 530,
"y": 310,
"wires": [["test_tts_debug"]]
},
{
"id": "test_tts_debug",
"type": "debug",
"z": "tab_test_clean_tts",
"name": "API Response",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 770,
"y": 230,
"wires": []
}
]
To send announcements directly with standard Node-RED nodes:
http request node to your flow:
POSThttp://127.0.0.1:8123/api/ttsa parsed JSON objectmsg.payload in a function or change node:// Standard announcement on iHost speaker
msg.payload = {
"text": "Motion detected in the backyard garden.",
"bell": true,
"volume": "+20%"
};
return msg;
For quiet night-time notifications without a chime:
msg.payload = {
"text": "Living room lights turned off. Good night!",
"bell": false,
"volume": "+0%",
"rate": "-5%"
};
return msg;
To stream audio directly to external DLNA / Sonos speakers or Home Assistant:
http://ihost.local:8123/api/tts?text=Welcome+home&lang=en&bell=true
In Home Assistant:
service: media_player.play_media
target:
entity_id: media_player.living_room_speaker
data:
media_content_id: "http://ihost.local:8123/api/tts?text=Front+door+opened&bell=true"
media_content_type: "music"
Clean TTS emulates the native Home Assistant MaryTTS protocol out of the box. No HACS or custom integrations required!
configuration.yaml:tts:
- platform: marytts
host: "<clean-tts-host>" # IP or hostname of your Clean TTS server or Sonoff iHost
port: 8123
voice: "en-US-JennyNeural" # Or any supported voice (e.g. ro-RO-AlinaNeural)
codec: "WAVE_FILE" # Or "MP3"
cache: false # Optional: set false to prevent Home Assistant from caching dynamic speech locally
Restart Home Assistant to apply the configuration.
action: tts.speak
target:
entity_id: tts.marytts
data:
media_player_entity_id: media_player.living_room_speaker
message: "Living room temperature is 22 degrees."
Clean TTS applies your phonetic dictionary corrections, loudness normalization, and chime bell automatically!
Access http://ihost.local:8123 from any browser on your network:
/): Test any text, preview voices, and adjust global volume and speed sliders./dict): Teach Clean TTS how to pronounce tricky local words or smart home brands with instant audio preview./backup): Download a full ZIP backup of your dictionaries and settings with 1 click./languages): Switch between English, Romanian, French, German, Spanish, Italian, and Catalan./mcp): Connect AI assistants like Claude Desktop to control speech hands-free.Built for the Sonoff iHost and home automation community to deliver clear, beautiful, and reliable smart home voice announcements.
Content type
Image
Digest
sha256:7072427bf…
Size
219.7 MB
Last updated
10 days ago
docker pull tmalex/clean-tts