13 Commits
0.1.5 ... 0.2.0

Author SHA1 Message Date
d09ff3d7cf custom_components/whatsapp_bridge_integration/sensor.py aktualisiert
0.2.0
2026-06-15 09:16:40 +00:00
fa7baddeb6 custom_components/whatsapp_bridge_integration/__init__.py aktualisiert 2026-06-15 09:16:13 +00:00
cff4183c46 custom_components/whatsapp_bridge_integration/manifest.json aktualisiert
0.2.0
2026-06-15 09:09:18 +00:00
719a4b5129 CHANGELOG.md
Add Changelog
2026-06-15 09:08:28 +00:00
c536a75f4c README.md aktualisiert 2026-04-14 16:19:46 +00:00
f346cea41a custom_components/whatsapp_bridge_integration/manifest.json aktualisiert 2026-04-14 14:02:20 +00:00
93b57878f9 custom_components/whatsapp_bridge_integration/services.yaml aktualisiert 2026-04-14 14:01:07 +00:00
fc826d7345 custom_components/whatsapp_bridge_integration/services.py aktualisiert 2026-04-14 14:00:54 +00:00
3002acc934 custom_components/whatsapp_bridge_integration/sensor.py aktualisiert 2026-04-14 14:00:36 +00:00
39df1ed0fa custom_components/whatsapp_bridge_integration/services.py aktualisiert 2026-04-14 13:59:19 +00:00
6b3a10bd33 custom_components/whatsapp_bridge_integration/services.yaml aktualisiert 2026-04-14 13:59:02 +00:00
ec6a9c6dd8 custom_components/whatsapp_bridge_integration/manifest.json aktualisiert 2026-04-14 13:48:34 +00:00
aa5a060aef custom_components/whatsapp_bridge_integration/services.yaml aktualisiert 2026-04-14 13:47:33 +00:00
7 changed files with 286 additions and 73 deletions

10
CHANGELOG.md Normal file
View File

@@ -0,0 +1,10 @@
# CHANGELOG - Whatsapp Bridge Integration
## Release 0.2.0 - 2026-06-15
- New: Setup of a main device (WhatsApp Bridge Gateway) in the Home Assistant device registry.
- New: Integrated global service `whatsapp_bridge.send_broadcast`, which operates independently of individual profile entities and can send messages to any number or group ID.
- New: Added status sensor (`sensor.whatsapp_bridge_status`). This queries the add-on's new API and provides the current connection status (`CONNECTED`, `WAITING_FOR_SCAN`, `INITIALIZING`, `OFFLINE`) directly within Home Assistant.
- Improved: The sensor is assigned to the new main device, which significantly simplifies automations (e.g., alert on connection loss).
## Release 0.1.7
- Previous stable version

128
README.md
View File

@@ -1,58 +1,58 @@
# WhatsApp Bridge Integration for Home Assistant 🚀 # WhatsApp Bridge for Home Assistant
This is the custom integration for the **Bahmcloud WhatsApp Bridge**. It allows you to manage multiple WhatsApp accounts as Home Assistant services, making it incredibly easy to send automated messages via YAML or Node-RED. A professional, seamless integration to send WhatsApp messages through the **Bahmcloud Bridge**. This integration supports multiple profiles, dynamic entity-based selection, and broadcast capabilities with native Home Assistant support.
--- ## 🚀 Installation
## 🛠️ Installation ### 1. Install the Add-on
The **Bahmcloud WhatsApp Bridge Add-on** must be running for the integration to work:
### 1. Prerequisite: The Add-on 1. Navigate to **Settings** -> **Add-ons**.
Before installing this integration, ensure that the **WhatsApp Bridge Add-on** is installed and running. 2. Click **Add-on Store** -> Three dots (top right) -> **Repositories**.
* [Link to your Add-on Repository] 3. Add your Bahmcloud Repository URL.
* Open the Add-on Web UI and scan the QR code to link your WhatsApp account. 4. Search for "WhatsApp Bridge", click **Install**, and **Start**.
### 2. Install the Integration ### 2. Install the Integration
1. Copy the folder `whatsapp_bridge_integration` into your Home Assistant `config/custom_components/` directory. You can install the integration component via three methods:
2. Restart Home Assistant.
3. Go to **Settings** -> **Devices & Services**. * **HACS (Recommended):** Add this repository as a "Custom Repository" in HACS and click Install.
4. Click **Add Integration** and search for "WhatsApp Bridge Integration". * **BCS (Bahmcloud Store):** Search for "WhatsApp Bridge" in the BCS interface.
* **Manual:** Copy the `custom_components/whatsapp_bridge_integration` folder into your `/config/custom_components/` directory and restart Home Assistant.
--- ---
## 📖 How to use ## 👥 Configuration & Profiles
Once installed, the integration provides a new service (action) that you can use in any automation. Add your WhatsApp accounts via the Home Assistant UI:
1. Go to **Settings** -> **Devices & Services**.
2. Click **Add Integration** and search for **WhatsApp Bridge**.
3. Follow the setup to add an **Account Name** (e.g., "Work") and your **Phone Number**.
4. **Repeat** this process for as many accounts as you need.
### Send a Message via YAML ### Resulting Entities
You can call the service directly in your automations: The integration automatically creates sensors for your UI:
* `sensor.whatsapp_account_name`: An entity for each individual account.
* `sensor.whatsapp_broadcast_all`: A global entity to message all accounts at once.
---
## 🛠 Usage
### Action: `send_message`
This integration registers a service/action to send messages.
**Fields:**
* **Recipient:** Select an account from the dropdown, choose the "Broadcast" entity, or manually type a phone number.
* **Message:** The text content you wish to send.
#### YAML Example:
```yaml ```yaml
action: whatsapp_bridge_integration.send_message action: whatsapp_bridge_integration.send_message
data: data:
number: "491701234567" recipient: "sensor.whatsapp_rene"
message: "The washing machine is finished! 🧺" message: "The garage door is still open!"
``` ```
### Use in Node-RED ## ⚠️ Important Notes
Add a Call Service (or Action) node.
Domain: whatsapp_bridge_integration
Service: send_message
Data:
```JSON
{
"number": "491701234567",
"message": "{{payload}}"
}
```
## 👥 Multi-Account Support (Coming Soon)
We are currently working on a UI-based configuration flow so you can save "Account Aliases" (like "Dad", "Work", "Security") and select them from a dropdown menu without typing the phone number every time.
##⚠️ Important Notes
Phone Numbers: Always use the international format without + or 00 (e.g., 49... for Germany). Phone Numbers: Always use the international format without + or 00 (e.g., 49... for Germany).
Local Network: The integration communicates locally with the Add-on on port 3000. Local Network: The integration communicates locally with the Add-on on port 3000.
@@ -60,3 +60,53 @@ Local Network: The integration communicates locally with the Add-on on port 3000
Developed by Bahmcloud Developed by Bahmcloud
--- ---
## 🤖 Node-RED Integration
The integration is fully compatible with Node-RED. Since it uses native entities, you can use the standard *Action (Call Service)* node.
### Example: Smart Window Warning
Notify yourself if a window or door has been left open for 15 minutes.
1. *Trigger*: Use a `State Trigger` or `Device` node.
2. *Logic (Change Node)*: Use JSONata to create a dynamic message:
- *Set*: msg.payload
- *Value (JSONata)*: `payload.to_state.attributes.friendly_name & " has been open for 15 minutes!"`
3. WhatsApp Node (Action Node):
- *Action*: `whatsapp_bridge_integration.send_message`
- *Data (JSON)*:
```JSON
{
"recipient": "sensor.whatsapp_broadcast_all",
"message": "{{payload}}"
}
```
### Manual Tool Calling
To send a message to a number not saved as a profile, simply pass the raw phone number to the recipient field in your Node-RED JSON:
```JSON
{
"recipient": "491701234567",
"message": "Direct alert to external number."
}
```
---
# ✨ Features
- *Dynamic UI*: Accounts are automatically added to the recipient dropdown.
- *One-Click Broadcast*: Special entity to reach all configured profiles instantly.
- *Manual Fallback*: Clean UI remains flexible; type any number to bypass profiles.
- *Developer Friendly*: Designed for easy integration with Node-RED and HA Automations.
---
Maintained by Bahmcloud

View File

@@ -1,5 +1,6 @@
import logging import logging
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from .const import DOMAIN from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -8,11 +9,22 @@ async def async_setup(hass: HomeAssistant, config: dict):
return True return True
async def async_setup_entry(hass: HomeAssistant, entry): async def async_setup_entry(hass: HomeAssistant, entry):
"""Set up entry and forward to sensor platform.""" """Set up entry, create general device and forward to sensor platform."""
# Lege die Daten im hass-Objekt ab # Lege die Daten im hass-Objekt ab
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = entry.data hass.data[DOMAIN][entry.entry_id] = entry.data
# 1. Hauptgerät (General Device) im Device Registry anlegen
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, "general_gateway")},
name="WhatsApp Bridge Gateway",
manufacturer="Bahmcloud",
model="Bridge Server v0.2.0",
sw_version="0.2.0",
)
# Forward the setup to the sensor platform to create entities # Forward the setup to the sensor platform to create entities
await hass.config_entries.async_forward_entry_setups(entry, ["sensor"]) await hass.config_entries.async_forward_entry_setups(entry, ["sensor"])

View File

@@ -4,7 +4,7 @@
"documentation": "https://git.bahmcloud.de/bahmcloud/Whatsapp-Bridge-Integration", "documentation": "https://git.bahmcloud.de/bahmcloud/Whatsapp-Bridge-Integration",
"dependencies": [], "dependencies": [],
"codeowners": ["@bahmcloud"], "codeowners": ["@bahmcloud"],
"version": "0.1.5", "version": "0.2.0",
"iot_class": "local_polling", "iot_class": "local_polling",
"config_flow": true "config_flow": true
} }

View File

@@ -1,22 +1,162 @@
import logging
import async_timeout
import aiohttp
from datetime import timedelta
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import SensorEntity
from .const import DOMAIN, CONF_ACCOUNT_NAME from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
# Polling-Intervall (15 Sekunden)
SCAN_INTERVAL = timedelta(seconds=15)
async def async_setup_entry(hass, entry, async_add_entities): async def async_setup_entry(hass, entry, async_add_entities):
"""Add a sensor for each WhatsApp account.""" """Add sensors for WhatsApp accounts, live status, and a global broadcast sensor."""
async_add_entities([WhatsAppAccountEntity(entry)], True)
# Coordinator für den echten Live-Status vom Add-on einrichten
async def _async_update_data():
url = "http://866fd2eb-whatsapp-bridge:3000/api/status"
async with aiohttp.ClientSession() as session:
try:
async with async_timeout.timeout(5):
async with session.get(url) as response:
if response.status != 200:
raise UpdateFailed(f"API meldet Fehler {response.status}")
return await response.json()
except Exception as err:
raise UpdateFailed(f"Add-on nicht erreichbar: {err}")
coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name="whatsapp_bridge_status",
update_method=_async_update_data,
update_interval=SCAN_INTERVAL,
)
# Ersten Abruf vor dem Erstellen der Entitäten erzwingen
try:
await coordinator.async_config_entry_first_refresh()
except UpdateFailed:
_LOGGER.warning("Erster Status-Abruf fehlgeschlagen. Add-on läuft eventuell noch an.")
# Basis-Entitäten: Account-Sensor und der neue Live-Status-Sensor
entities = [
WhatsAppAccountEntity(entry),
WhatsAppStatusSensor(coordinator, entry)
]
# Prüfen, ob die Broadcast-Entität schon existiert
if not hass.data[DOMAIN].get("broadcast_registered"):
entities.append(WhatsAppBroadcastEntity())
hass.data[DOMAIN]["broadcast_registered"] = True
async_add_entities(entities, True)
class WhatsAppAccountEntity(SensorEntity): class WhatsAppAccountEntity(SensorEntity):
"""Representation of a WhatsApp Account as an entity.""" """Individual Account (e.g., Rene, Work)."""
def __init__(self, entry): def __init__(self, entry):
self._entry = entry self._entry = entry
self._attr_name = f"WhatsApp {entry.title}" self._attr_name = f"WhatsApp {entry.title}"
self._attr_unique_id = f"{entry.entry_id}_status" self._attr_unique_id = f"{entry.entry_id}_status"
self._attr_icon = "mdi:whatsapp" self._attr_icon = "mdi:whatsapp"
# Wir speichern die Nummer in den Attributen, damit der Service sie findet
self._attr_extra_state_attributes = { self._attr_extra_state_attributes = {
"phone_number": entry.data.get("phone_number") "phone_number": entry.data.get("phone_number"),
"is_broadcast": False
} }
@property @property
def state(self): def state(self):
return "Ready" return "Ready"
@property
def device_info(self) -> DeviceInfo:
"""Ordnet diesen Sensor dem Hauptgerät zu."""
return DeviceInfo(
identifiers={(DOMAIN, "general_gateway")},
)
class WhatsAppBroadcastEntity(SensorEntity):
"""Virtual 'All Accounts' Entity."""
def __init__(self):
self._attr_name = "WhatsApp Broadcast (All)"
self._attr_unique_id = "whatsapp_bridge_broadcast_all"
self._attr_icon = "mdi:whatsapp-arrow-up"
self._attr_extra_state_attributes = {
"is_broadcast": True
}
@property
def state(self):
return "Global"
@property
def device_info(self) -> DeviceInfo:
"""Ordnet diesen Sensor dem Hauptgerät zu."""
return DeviceInfo(
identifiers={(DOMAIN, "general_gateway")},
)
class WhatsAppStatusSensor(SensorEntity):
"""Echter Live-Status-Sensor, gekoppelt an die Add-on API."""
def __init__(self, coordinator, entry):
self.coordinator = coordinator
self._entry = entry
self._attr_name = "WhatsApp Bridge Status"
self._attr_unique_id = f"{entry.entry_id}_bridge_live_status"
self._attr_icon = "mdi:whatsapp"
@property
def should_poll(self) -> bool:
return False
@property
def available(self) -> bool:
return self.coordinator.last_update_success
@property
def state(self):
"""Gibt den dynamischen Status aus der Add-on API zurück."""
data = self.coordinator.data
if not data:
return "OFFLINE"
if data.get("isReady"):
return "CONNECTED"
elif data.get("hasQr"):
return "WAITING_FOR_SCAN"
elif data.get("isInitializing"):
return "INITIALIZING"
return "UNKNOWN"
@property
def extra_state_attributes(self):
"""Zusätzliche Attribute für erweitertes Debugging."""
data = self.coordinator.data
if data:
return {
"client_state": data.get("clientState", "UNKNOWN"),
"has_qr_code": data.get("hasQr", False)
}
return {}
@property
def device_info(self) -> DeviceInfo:
"""Ordnet diesen Sensor dem Hauptgerät zu."""
return DeviceInfo(
identifiers={(DOMAIN, "general_gateway")},
)
async def async_added_to_hass(self):
"""Registrieren beim Live-Coordinator."""
self.async_on_remove(
self.coordinator.async_add_listener(self.async_write_ha_state)
)

View File

@@ -1,43 +1,44 @@
import requests import requests
import logging import logging
from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.core import HomeAssistant, ServiceCall
from .const import DOMAIN from .const import DOMAIN, CONF_PHONE_NUMBER
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_services(hass: HomeAssistant): async def async_setup_services(hass: HomeAssistant):
async def handle_send_message(call: ServiceCall): async def handle_send_message(call: ServiceCall):
message = call.data.get("message") message = call.data.get("message")
target_entity = call.data.get("recipient") recipient = call.data.get("recipient")
targets = [] targets = []
entries = hass.config_entries.async_entries(DOMAIN)
# 1. Fall: Broadcast an alle # 1. Prüfen, ob es eine Entity-ID ist
if target_entity == "all": if recipient.startswith("sensor."):
entities = hass.states.async_all("sensor") state = hass.states.get(recipient)
for entity in entities:
if entity.entity_id.startswith("sensor.whatsapp_"):
targets.append(entity.attributes.get("phone_number"))
# 2. Fall: Einzelne Entity ausgewählt
else:
state = hass.states.get(target_entity)
if state: if state:
num = state.attributes.get("phone_number") # Ist es die 'All Accounts' Entität?
if num: if state.attributes.get("is_broadcast"):
targets.append(num) for e in entries:
else: targets.append(e.data.get(CONF_PHONE_NUMBER))
# Fallback für manuelle Eingabe in YAML else:
targets.append(target_entity) # Es ist ein einzelner Account
targets.append(state.attributes.get("phone_number"))
# 2. Fallback: Manuelle Nummer (wenn keine sensor-ID übergeben wurde)
else:
targets.append(recipient)
# Senden
for num in targets: for num in targets:
if not num: continue
url = "http://866fd2eb-whatsapp-bridge:3000/send" url = "http://866fd2eb-whatsapp-bridge:3000/send"
try: try:
await hass.async_add_executor_job( await hass.async_add_executor_job(
lambda: requests.post(url, json={"number": num, "message": message}, timeout=10) lambda: requests.post(url, json={"number": num, "message": message}, timeout=10)
) )
_LOGGER.info("Sent to %s", num) _LOGGER.info("WhatsApp sent to %s", num)
except Exception as e: except Exception as e:
_LOGGER.error("Error: %s", str(e)) _LOGGER.error("Error sending to %s: %s", num, str(e))
hass.services.async_register(DOMAIN, "send_message", handle_send_message) hass.services.async_register(DOMAIN, "send_message", handle_send_message)

View File

@@ -1,20 +1,20 @@
send_message: send_message:
name: Send Message name: Send Message
description: Sends a WhatsApp message. description: Sends a WhatsApp message via the Bridge.
fields: fields:
recipient: recipient:
name: Recipient name: Recipient
description: Select an account or enter a manual number/ID. description: Select an Account, the Broadcast entity, or type a manual number.
required: true required: true
selector: selector:
text: {} # Easy Entry from some custom Numbers # Dies zeigt nun "WhatsApp Rene", "WhatsApp Work" UND "WhatsApp Broadcast (All)" an.
entity: entity:
filter: filter:
integration: whatsapp_bridge_integration integration: whatsapp_bridge_integration
domain: sensor domain: sensor
message: message:
name: Message name: Message
description: The content of your message. description: Your message text.
required: true required: true
selector: selector:
text: text: