Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d85bd7905 | |||
| d4ede6669a | |||
| eb6be8cdf4 | |||
| 2c62edfa44 | |||
| faed01cd8f | |||
| 7175522f4a | |||
| 3b99c037ae | |||
| 4f8a4304ba | |||
| bcff98b44b | |||
| 40f74f9b8b | |||
| 5e1c07cecf | |||
| 2cfb881cc0 | |||
| c5d216e458 | |||
| 4f08cbbd6d | |||
| d09ff3d7cf | |||
| fa7baddeb6 | |||
| cff4183c46 | |||
| 719a4b5129 | |||
| c536a75f4c | |||
| f346cea41a | |||
| 93b57878f9 | |||
| fc826d7345 | |||
| 3002acc934 | |||
| 39df1ed0fa | |||
| 6b3a10bd33 | |||
| ec6a9c6dd8 | |||
| aa5a060aef | |||
| 7bea9bbbbb | |||
| 8dc6cfb7c1 | |||
| b4fd722d8a | |||
| 20e6090f65 | |||
| d9ada23f3e | |||
| 276d4a63cb | |||
| adfe5dfe9d | |||
| 037cd9c9c7 | |||
| 498c059aa8 | |||
| dbdf987501 | |||
| 370e33da7c |
24
CHANGELOG.md
Normal file
24
CHANGELOG.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# CHANGELOG - Whatsapp Bridge Integration
|
||||||
|
|
||||||
|
## Release 0.2.3 - 2026-06-15
|
||||||
|
- **New (Orphaned Devices Cleaner):** Added an automatic database cleanup utility. On startup, the integration now automatically identifies and removes orphaned or "ghost" devices left behind by previous configuration changes or older versions.
|
||||||
|
- **Fix:** Decoupled the central "WhatsApp Bridge Hub" device from specific config entry bindings. The Hub is now persistent and indestructible, preventing global entities (Broadcast and Live Status) from disappearing when individual user profiles are deleted or re-added.
|
||||||
|
|
||||||
|
## Release 0.2.2 - 2026-06-15
|
||||||
|
- **Architecture Change:** Introduced a dedicated "WhatsApp Bridge Hub" device. This central device acts as a single point of reference for global components, preventing UI clutter.
|
||||||
|
- **Fix:** Moved the global live status sensor and the virtual broadcast entity to the new WhatsApp Bridge Hub device. This ensures they remain fully visible and accessible across the Home Assistant UI, automations, and service calls without being duplicated across individual user accounts.
|
||||||
|
- **Improvement:** Maintained isolated, clean individual devices for each configured account profile.
|
||||||
|
|
||||||
|
## Release 0.2.1 - 2026-06-15
|
||||||
|
- **Fix:** Resolved device assignment duplication where a single global gateway card duplicated across all account tabs.
|
||||||
|
- **Fix:** Globalized the live status sensor and the broadcast entity to instantiate exactly once for the entire integration rather than incorrectly duplicating per account.
|
||||||
|
- **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
|
||||||
130
README.md
130
README.md
@@ -1,62 +1,112 @@
|
|||||||
# 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.
|
||||||
|
|
||||||
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
|
||||||
@@ -1,50 +1,70 @@
|
|||||||
import logging
|
import logging
|
||||||
import requests
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall
|
from homeassistant.helpers import device_registry as dr
|
||||||
from .const import DOMAIN, CONF_PHONE_NUMBER, CONF_ACCOUNT_NAME
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
async def async_setup(hass: HomeAssistant, config: dict):
|
||||||
|
return True
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry):
|
async def async_setup_entry(hass: HomeAssistant, entry):
|
||||||
"""Set up a profile from a config entry."""
|
"""Set up entry, create devices, and clean up old orphaned devices."""
|
||||||
|
hass.data.setdefault(DOMAIN, {})
|
||||||
|
hass.data[DOMAIN][entry.entry_id] = entry.data
|
||||||
|
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
|
|
||||||
|
# 1. Automatisches Aufräumen von alten Geister-Geräten
|
||||||
|
active_entry_ids = set(hass.config_entries.async_entries(DOMAIN))
|
||||||
|
active_ids = {e.entry_id for e in active_entry_ids}
|
||||||
|
|
||||||
|
# Durchsuche die HA-Datenbank nach Geräten unserer Integration
|
||||||
|
devices_to_clean = [
|
||||||
|
dev for dev in device_registry.devices.values()
|
||||||
|
if any(identifier[0] == DOMAIN for identifier in dev.identifiers)
|
||||||
|
]
|
||||||
|
|
||||||
|
for device in devices_to_clean:
|
||||||
|
# Falls ein Gerät zu einem gelöschten Config Entry gehört -> Löschen!
|
||||||
|
# Der globale Hub ("global_bridge_hub") wird ignoriert und bleibt erhalten.
|
||||||
|
for config_id in device.config_entries:
|
||||||
|
if config_id not in active_ids and not any(idx == "global_bridge_hub" for _, idx in device.identifiers):
|
||||||
|
_LOGGER.info(f"Entferne verwaistes WhatsApp-Gerät: {device.name}")
|
||||||
|
device_registry.async_remove_device(device.id)
|
||||||
|
|
||||||
|
# 2. Eigenes Gerät SPEZIFISCH für dieses aktive Konto anlegen
|
||||||
|
device_registry.async_get_or_create(
|
||||||
|
config_entry_id=entry.entry_id,
|
||||||
|
identifiers={(DOMAIN, entry.entry_id)},
|
||||||
|
name=f"WhatsApp {entry.title}",
|
||||||
|
manufacturer="Bahmcloud",
|
||||||
|
model="Kontakt-Endpunkt",
|
||||||
|
sw_version="0.2.3",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. Zentralen System-Hub VOLLKOMMEN UNABHÄNGIG anlegen (ohne feste config_entry_id Bindung)
|
||||||
|
device_registry.async_get_or_create(
|
||||||
|
identifiers={(DOMAIN, "global_bridge_hub")},
|
||||||
|
name="WhatsApp Bridge Hub",
|
||||||
|
manufacturer="Bahmcloud",
|
||||||
|
model="Bridge Server",
|
||||||
|
sw_version="0.2.3",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Forward the setup to the sensor platform to create entities
|
||||||
|
await hass.config_entries.async_forward_entry_setups(entry, ["sensor"])
|
||||||
|
|
||||||
|
# Registriere den Service (nur beim ersten Mal)
|
||||||
if not hass.services.has_service(DOMAIN, "send_message"):
|
if not hass.services.has_service(DOMAIN, "send_message"):
|
||||||
|
from .services import async_setup_services
|
||||||
async def handle_send_message(call: ServiceCall):
|
await async_setup_services(hass)
|
||||||
message = call.data.get("message")
|
|
||||||
target_account = call.data.get("account")
|
|
||||||
target_number = call.data.get("number")
|
|
||||||
all_accounts = call.data.get("all_accounts", False)
|
|
||||||
|
|
||||||
entries = hass.config_entries.async_entries(DOMAIN)
|
|
||||||
targets = []
|
|
||||||
|
|
||||||
if all_accounts:
|
|
||||||
for e in entries:
|
|
||||||
targets.append(e.data.get(CONF_PHONE_NUMBER))
|
|
||||||
elif target_number:
|
|
||||||
targets.append(target_number)
|
|
||||||
elif target_account:
|
|
||||||
for e in entries:
|
|
||||||
if e.title.lower() == target_account.lower():
|
|
||||||
targets.append(e.data.get(CONF_PHONE_NUMBER))
|
|
||||||
|
|
||||||
for num in targets:
|
|
||||||
url = "http://866fd2eb-whatsapp-bridge:3000/send"
|
|
||||||
try:
|
|
||||||
await hass.async_add_executor_job(
|
|
||||||
lambda: requests.post(url, json={
|
|
||||||
"number": num,
|
|
||||||
"message": message
|
|
||||||
}, timeout=10)
|
|
||||||
)
|
|
||||||
_LOGGER.info("WhatsApp sent to %s", num)
|
|
||||||
except Exception as e:
|
|
||||||
_LOGGER.error("Failed to send to %s: %s", num, str(e))
|
|
||||||
|
|
||||||
hass.services.async_register(DOMAIN, "send_message", handle_send_message)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def async_unload_entry(hass, entry):
|
async def async_unload_entry(hass: HomeAssistant, entry):
|
||||||
return True
|
"""Unload a config entry."""
|
||||||
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, ["sensor"])
|
||||||
|
if unload_ok:
|
||||||
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
return unload_ok
|
||||||
@@ -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.2",
|
"version": "0.2.3",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"config_flow": true
|
"config_flow": true
|
||||||
}
|
}
|
||||||
157
custom_components/whatsapp_bridge_integration/sensor.py
Normal file
157
custom_components/whatsapp_bridge_integration/sensor.py
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import logging
|
||||||
|
import async_timeout
|
||||||
|
import aiohttp
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import SensorEntity
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SCAN_INTERVAL = timedelta(seconds=15)
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, entry, async_add_entities):
|
||||||
|
"""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)]
|
||||||
|
|
||||||
|
# Globale Entitäten (Status & Broadcast) NUR EINMALIG für die gesamte Integration anlegen
|
||||||
|
if not hass.data[DOMAIN].get("global_entities_registered"):
|
||||||
|
entities.append(WhatsAppStatusSensor(coordinator))
|
||||||
|
entities.append(WhatsAppBroadcastEntity())
|
||||||
|
hass.data[DOMAIN]["global_entities_registered"] = True
|
||||||
|
|
||||||
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
|
class WhatsAppAccountEntity(SensorEntity):
|
||||||
|
"""Individual Account (e.g., Rene, Work)."""
|
||||||
|
def __init__(self, entry):
|
||||||
|
self._entry = entry
|
||||||
|
self._attr_name = f"WhatsApp {entry.title}"
|
||||||
|
self._attr_unique_id = f"{entry.entry_id}_status"
|
||||||
|
self._attr_icon = "mdi:whatsapp"
|
||||||
|
self._attr_extra_state_attributes = {
|
||||||
|
"phone_number": entry.data.get("phone_number"),
|
||||||
|
"is_broadcast": False
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
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):
|
||||||
|
"""Virtual 'All Accounts' Entity, geordnet unter dem zentralen Hub."""
|
||||||
|
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 diese Entität dem permanenten, zentralen Hub-Gerät zu."""
|
||||||
|
return DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, "global_bridge_hub")},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WhatsAppStatusSensor(SensorEntity):
|
||||||
|
"""Echter Live-Status-Sensor, geordnet unter dem zentralen Hub."""
|
||||||
|
|
||||||
|
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 {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self) -> DeviceInfo:
|
||||||
|
"""Ordnet diese Entität dem permanenten, zentralen Hub-Gerät zu."""
|
||||||
|
return DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, "global_bridge_hub")},
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
)
|
||||||
44
custom_components/whatsapp_bridge_integration/services.py
Normal file
44
custom_components/whatsapp_bridge_integration/services.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import requests
|
||||||
|
import logging
|
||||||
|
from homeassistant.core import HomeAssistant, ServiceCall
|
||||||
|
from .const import DOMAIN, CONF_PHONE_NUMBER
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
async def async_setup_services(hass: HomeAssistant):
|
||||||
|
async def handle_send_message(call: ServiceCall):
|
||||||
|
message = call.data.get("message")
|
||||||
|
recipient = call.data.get("recipient")
|
||||||
|
|
||||||
|
targets = []
|
||||||
|
entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
|
|
||||||
|
# 1. Prüfen, ob es eine Entity-ID ist
|
||||||
|
if recipient.startswith("sensor."):
|
||||||
|
state = hass.states.get(recipient)
|
||||||
|
if state:
|
||||||
|
# Ist es die 'All Accounts' Entität?
|
||||||
|
if state.attributes.get("is_broadcast"):
|
||||||
|
for e in entries:
|
||||||
|
targets.append(e.data.get(CONF_PHONE_NUMBER))
|
||||||
|
else:
|
||||||
|
# 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:
|
||||||
|
if not num: continue
|
||||||
|
url = "http://866fd2eb-whatsapp-bridge:3000/send"
|
||||||
|
try:
|
||||||
|
await hass.async_add_executor_job(
|
||||||
|
lambda: requests.post(url, json={"number": num, "message": message}, timeout=10)
|
||||||
|
)
|
||||||
|
_LOGGER.info("WhatsApp sent to %s", num)
|
||||||
|
except Exception as e:
|
||||||
|
_LOGGER.error("Error sending to %s: %s", num, str(e))
|
||||||
|
|
||||||
|
hass.services.async_register(DOMAIN, "send_message", handle_send_message)
|
||||||
@@ -1,30 +1,21 @@
|
|||||||
send_message:
|
send_message:
|
||||||
name: Send Message
|
name: Send Message
|
||||||
description: Sends a WhatsApp message via the Bahmcloud Bridge.
|
description: Sends a WhatsApp message via the Bridge.
|
||||||
fields:
|
fields:
|
||||||
account:
|
recipient:
|
||||||
name: Account Profile
|
name: Recipient
|
||||||
description: The name of a saved profile (e.g., Max). Leave empty if using a manual number.
|
description: Select an Account, the Broadcast entity, or type a manual number.
|
||||||
example: "Max"
|
required: true
|
||||||
selector:
|
selector:
|
||||||
text:
|
# Dies zeigt nun "WhatsApp Rene", "WhatsApp Work" UND "WhatsApp Broadcast (All)" an.
|
||||||
number:
|
entity:
|
||||||
name: Manual Number
|
filter:
|
||||||
description: "Direct phone number (Format: 491701234567). Overwrites the Account field."
|
integration: whatsapp_bridge_integration
|
||||||
example: "491701234567"
|
domain: sensor
|
||||||
selector:
|
|
||||||
text:
|
|
||||||
message:
|
message:
|
||||||
name: Message
|
name: Message
|
||||||
description: The content of your WhatsApp message.
|
description: Your message text.
|
||||||
required: true
|
required: true
|
||||||
example: "The garage door is open!"
|
|
||||||
selector:
|
selector:
|
||||||
text:
|
text:
|
||||||
multiline: true
|
multiline: true
|
||||||
all_accounts:
|
|
||||||
name: Send to All
|
|
||||||
description: If enabled, the message is sent to all registered profiles.
|
|
||||||
default: false
|
|
||||||
selector:
|
|
||||||
boolean:
|
|
||||||
Reference in New Issue
Block a user