10 Commits
0.1.7 ... 0.2.1

5 changed files with 225 additions and 49 deletions

15
CHANGELOG.md Normal file
View File

@@ -0,0 +1,15 @@
# CHANGELOG - Whatsapp Bridge Integration
## Release 0.2.1 - 2026-06-15
- **Fix:** Resolved device assignment duplication. Instead of a rigid global gateway card duplicating across all account tabs, a distinct and separate Home Assistant device is now generated for each configured account (*Larissa, Rene, Work*).
- **Fix:** Globalized the live status sensor and the broadcast entity. They are now instantiated exactly once for the entire integration (rather than incorrectly duplicating per account) and exist as free entities without device assignment to maintain a clean UI layout.
- **Optimization:** Code refactoring within `sensor.py` to prevent entity duplication when utilizing multiple profiles.
## 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

126
README.md
View File

@@ -1,57 +1,57 @@
# 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
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 ## ⚠️ 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).
@@ -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 specific contact 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. Eigenes Gerät SPEZIFISCH für diesen Account/Kontakt anlegen
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, entry.entry_id)}, # Einzigartig pro Eintrag
name=f"WhatsApp {entry.title}",
manufacturer="Bahmcloud",
model="Kontakt-Endpunkt",
sw_version="0.2.1",
)
# 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.7", "version": "0.2.1",
"iot_class": "local_polling", "iot_class": "local_polling",
"config_flow": true "config_flow": true
} }

View File

@@ -1,18 +1,58 @@
import logging
import async_timeout
import aiohttp
from datetime import timedelta
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import SensorEntity
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
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 sensors for WhatsApp accounts and a global broadcast sensor.""" """Add sensors for WhatsApp accounts, live status, and a global broadcast sensor."""
# 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,
)
try:
await coordinator.async_config_entry_first_refresh()
except UpdateFailed:
_LOGGER.warning("Erster Status-Abruf fehlgeschlagen. Add-on läuft eventuell noch an.")
# Jeder Account bekommt immer seine eigene Entität
entities = [WhatsAppAccountEntity(entry)] entities = [WhatsAppAccountEntity(entry)]
# Wir prüfen, ob die Broadcast-Entität schon existiert # Globale Entitäten (Status & Broadcast) NUR EINMALIG für die gesamte Integration anlegen
# Falls nicht, legen wir sie einmalig an if not hass.data[DOMAIN].get("global_entities_registered"):
if not hass.data[DOMAIN].get("broadcast_registered"): entities.append(WhatsAppStatusSensor(coordinator))
entities.append(WhatsAppBroadcastEntity()) entities.append(WhatsAppBroadcastEntity())
hass.data[DOMAIN]["broadcast_registered"] = True hass.data[DOMAIN]["global_entities_registered"] = True
async_add_entities(entities, True) async_add_entities(entities, True)
class WhatsAppAccountEntity(SensorEntity): class WhatsAppAccountEntity(SensorEntity):
"""Individual Account (e.g., Rene, Work).""" """Individual Account (e.g., Rene, Work)."""
def __init__(self, entry): def __init__(self, entry):
@@ -29,8 +69,16 @@ class WhatsAppAccountEntity(SensorEntity):
def state(self): def state(self):
return "Ready" return "Ready"
@property
def device_info(self) -> DeviceInfo:
"""Ordnet diesen Sensor seinem spezifischen Personen-Gerät zu."""
return DeviceInfo(
identifiers={(DOMAIN, self._entry.entry_id)},
)
class WhatsAppBroadcastEntity(SensorEntity): class WhatsAppBroadcastEntity(SensorEntity):
"""Virtual 'All Accounts' Entity.""" """Virtual 'All Accounts' Entity (Global, keinem spezifischen Gerät zugeordnet)."""
def __init__(self): def __init__(self):
self._attr_name = "WhatsApp Broadcast (All)" self._attr_name = "WhatsApp Broadcast (All)"
self._attr_unique_id = "whatsapp_bridge_broadcast_all" self._attr_unique_id = "whatsapp_bridge_broadcast_all"
@@ -42,3 +90,54 @@ class WhatsAppBroadcastEntity(SensorEntity):
@property @property
def state(self): def state(self):
return "Global" return "Global"
class WhatsAppStatusSensor(SensorEntity):
"""Echter Live-Status-Sensor (Global, keinem spezifischen Gerät zugeordnet)."""
def __init__(self, coordinator):
self.coordinator = coordinator
self._attr_name = "WhatsApp Bridge Status"
self._attr_unique_id = "whatsapp_bridge_live_status_global"
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 {}
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)
)