Add Home Assistant custom integration and ESP availability tracking
- Add custom HA integration (config flow, switch, water_for service) - Add MQTT Last Will & Testament for offline detection - Publish 'online' on successful MQTT connect - Update README with integration docs
This commit is contained in:
@@ -1,15 +1,41 @@
|
|||||||
# Irrigation with an ESP8266
|
# Irrigation with an ESP8266
|
||||||
Tested with D1 mini. Connect a pump with a relais. Relais activation on D4.
|
Tested with D1 mini. Connect a pump with a relais. Relais activation on D4.
|
||||||
|
|
||||||
## Homeassistant integration
|
## Home Assistant Integration
|
||||||
payload_on can also contain the duration of the pump in ms.
|
|
||||||
Example:
|
### Custom Integration (recommended)
|
||||||
`
|
|
||||||
switch 5:
|
A full custom integration with config flow, device registry, and a `water_for` service is included in the [`homeassistant/`](homeassistant/) directory.
|
||||||
- platform: mqtt
|
|
||||||
command_topic: greenhousino/pump
|
See [homeassistant/README.md](homeassistant/README.md) for installation and usage.
|
||||||
state_topic: greenhousino/pumpstate
|
|
||||||
payload_on: "on"
|
### YAML-only (built-in MQTT platform)
|
||||||
payload_off: "off"
|
|
||||||
name: "Pumpe"
|
`payload_on` can also contain the duration of the pump in ms.
|
||||||
`
|
|
||||||
|
```yaml
|
||||||
|
switch:
|
||||||
|
- platform: mqtt
|
||||||
|
name: "Irrigation Pump"
|
||||||
|
command_topic: greenhousino/pump
|
||||||
|
state_topic: greenhousino/pumpstate
|
||||||
|
payload_on: "on"
|
||||||
|
payload_off: "off"
|
||||||
|
state_on: "on"
|
||||||
|
state_off: "off"
|
||||||
|
availability_topic: greenhousino/pump/status
|
||||||
|
payload_available: "online"
|
||||||
|
payload_not_available: "offline"
|
||||||
|
qos: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
For duration-based watering via YAML, use an automation:
|
||||||
|
```yaml
|
||||||
|
automation:
|
||||||
|
- alias: "Water for 5 minutes"
|
||||||
|
action:
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: greenhousino/pump
|
||||||
|
payload: "300000" # 300000 ms = 5 minutes
|
||||||
|
```
|
||||||
124
irrigation/homeassistant/README.md
Normal file
124
irrigation/homeassistant/README.md
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# Greenhousino Irrigation — Home Assistant Integration
|
||||||
|
|
||||||
|
Custom Home Assistant integration for the Greenhousino ESP8266/ESP32 irrigation controller.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Pump switch** — Turn the irrigation pump on/off from Home Assistant
|
||||||
|
- **State tracking** — Real-time pump state via MQTT
|
||||||
|
- **Availability** — Shows online/offline status
|
||||||
|
- **`water_for` service** — Water for a specific duration (1–3600 seconds)
|
||||||
|
- **Config flow** — Set up via Home Assistant UI (Settings → Devices & Services → Add Integration)
|
||||||
|
- **Device registry** — Appears as a proper device with manufacturer/model info
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Option 1: HACS (recommended)
|
||||||
|
|
||||||
|
1. Install [HACS](https://hacs.xyz/) in Home Assistant
|
||||||
|
2. Add this repository as a custom repository
|
||||||
|
3. Search for "Greenhousino Irrigation" and install
|
||||||
|
4. Restart Home Assistant
|
||||||
|
|
||||||
|
### Option 2: Manual installation
|
||||||
|
|
||||||
|
1. Copy the `greenhousino` folder to your Home Assistant config directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp -r greenhousino /config/custom_components/
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Restart Home Assistant
|
||||||
|
|
||||||
|
3. Go to **Settings → Devices & Services → Add Integration** and search for "Greenhousino Irrigation"
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The config flow will guide you through two steps:
|
||||||
|
|
||||||
|
### Step 1: Host
|
||||||
|
- **Host IP address** — The IP of your ESP device (e.g., `192.168.178.50`). This is used for the web UI link in device info.
|
||||||
|
|
||||||
|
### Step 2: MQTT Topics
|
||||||
|
- **Command topic** — MQTT topic to send commands (default: `greenhousino/pump`)
|
||||||
|
- **State topic** — MQTT topic for state updates (default: `greenhousino/pumpstate`)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Switch entity
|
||||||
|
|
||||||
|
The integration creates a switch entity (`switch.greenhousino_irrigation_pump`) that you can:
|
||||||
|
- Toggle on/off from the Home Assistant UI
|
||||||
|
- Use in automations
|
||||||
|
- Add to dashboards
|
||||||
|
|
||||||
|
### `water_for` service
|
||||||
|
|
||||||
|
Water for a specific duration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
service: greenhousino.water_for
|
||||||
|
data:
|
||||||
|
duration: 60 # seconds
|
||||||
|
```
|
||||||
|
|
||||||
|
Or from an automation:
|
||||||
|
```yaml
|
||||||
|
automation:
|
||||||
|
- alias: "Water garden every morning"
|
||||||
|
trigger:
|
||||||
|
- platform: time
|
||||||
|
at: "06:00:00"
|
||||||
|
action:
|
||||||
|
- service: greenhousino.water_for
|
||||||
|
data:
|
||||||
|
duration: 300 # 5 minutes
|
||||||
|
```
|
||||||
|
|
||||||
|
## MQTT Topics
|
||||||
|
|
||||||
|
| Direction | Topic | Payload | Description |
|
||||||
|
|-----------|-------|---------|-------------|
|
||||||
|
| HA → ESP | `greenhousino/pump` | `on` | Turn pump on (default 30s) |
|
||||||
|
| HA → ESP | `greenhousino/pump` | `off` | Turn pump off |
|
||||||
|
| HA → ESP | `greenhousino/pump` | `5000` | Turn pump on for 5000ms |
|
||||||
|
| ESP → HA | `greenhousino/pumpstate` | `on` | Pump is active |
|
||||||
|
| ESP → HA | `greenhousino/pumpstate` | `off` | Pump is off |
|
||||||
|
| ESP → HA | `greenhousino/pump/status` | `offline` | Device disconnected |
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Home Assistant 2024.x or later
|
||||||
|
- MQTT integration configured and connected to the same broker as your ESP device
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Switch shows "unavailable"
|
||||||
|
- Make sure the MQTT integration is connected
|
||||||
|
- Verify the ESP device is publishing to the state topic
|
||||||
|
- Check that topics match between the ESP firmware and HA config
|
||||||
|
|
||||||
|
### Service call doesn't work
|
||||||
|
- Ensure MQTT integration is set up
|
||||||
|
- Check HA logs: `Logger → greenhousino`
|
||||||
|
- Verify the ESP is subscribed to the command topic
|
||||||
|
|
||||||
|
## YAML-only alternative (no custom component)
|
||||||
|
|
||||||
|
If you don't want a custom integration, you can use the built-in MQTT platform:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
switch:
|
||||||
|
- platform: mqtt
|
||||||
|
name: "Irrigation Pump"
|
||||||
|
command_topic: "greenhousino/pump"
|
||||||
|
state_topic: "greenhousino/pumpstate"
|
||||||
|
payload_on: "on"
|
||||||
|
payload_off: "off"
|
||||||
|
state_on: "on"
|
||||||
|
state_off: "off"
|
||||||
|
availability_topic: "greenhousino/pump/status"
|
||||||
|
payload_available: "online"
|
||||||
|
payload_not_available: "offline"
|
||||||
|
qos: 1
|
||||||
|
```
|
||||||
143
irrigation/homeassistant/greenhousino/__init__.py
Normal file
143
irrigation/homeassistant/greenhousino/__init__.py
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
"""Greenhousino Irrigation integration."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
CONF_COMMAND_TOPIC,
|
||||||
|
CONF_HOST,
|
||||||
|
CONF_STATE_TOPIC,
|
||||||
|
DEFAULT_COMMAND_TOPIC,
|
||||||
|
DEFAULT_STATE_TOPIC,
|
||||||
|
DOMAIN,
|
||||||
|
MANUFACTURER,
|
||||||
|
MODEL,
|
||||||
|
SERVICE_DATA_DURATION,
|
||||||
|
SERVICE_WATER_FOR,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
PLATFORMS = ["switch"]
|
||||||
|
|
||||||
|
# Service schema
|
||||||
|
WATER_FOR_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(SERVICE_DATA_DURATION): vol.All(
|
||||||
|
vol.Coerce(float),
|
||||||
|
vol.Range(min=1, max=3600),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up Greenhousino from a config entry."""
|
||||||
|
hass_data = entry.runtime_data = GreenhousinoData(entry)
|
||||||
|
|
||||||
|
host = entry.data.get(CONF_HOST, "")
|
||||||
|
|
||||||
|
# Register device in HA device registry
|
||||||
|
await _register_device(hass, entry, host)
|
||||||
|
|
||||||
|
# Store entry data for service access
|
||||||
|
if DOMAIN not in hass.data:
|
||||||
|
hass.data[DOMAIN] = {"entries": {}}
|
||||||
|
|
||||||
|
hass.data[DOMAIN]["entries"][entry.entry_id] = hass_data
|
||||||
|
|
||||||
|
# Register the water_for service (only once)
|
||||||
|
if "service_registered" not in hass.data[DOMAIN]:
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_WATER_FOR,
|
||||||
|
_make_water_for_handler(hass),
|
||||||
|
schema=WATER_FOR_SCHEMA,
|
||||||
|
)
|
||||||
|
hass.data[DOMAIN]["service_registered"] = True
|
||||||
|
_LOGGER.info("Registered %s.%s service", DOMAIN, SERVICE_WATER_FOR)
|
||||||
|
|
||||||
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Unload a config entry."""
|
||||||
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
|
|
||||||
|
if unload_ok:
|
||||||
|
hass.data[DOMAIN]["entries"].pop(entry.entry_id, None)
|
||||||
|
|
||||||
|
if len(hass.data[DOMAIN]["entries"]) == 0:
|
||||||
|
hass.services.async_remove(DOMAIN, SERVICE_WATER_FOR)
|
||||||
|
hass.data[DOMAIN].clear()
|
||||||
|
|
||||||
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
def _make_water_for_handler(hass: HomeAssistant):
|
||||||
|
"""Create the water_for service handler with hass in closure."""
|
||||||
|
|
||||||
|
async def async_water_for(call) -> None:
|
||||||
|
"""Handle the water_for service call."""
|
||||||
|
duration_seconds = call.data[SERVICE_DATA_DURATION]
|
||||||
|
duration_ms = int(duration_seconds * 1000)
|
||||||
|
|
||||||
|
# Use first available command topic (or default)
|
||||||
|
entries = hass.data[DOMAIN].get("entries", {})
|
||||||
|
command_topic = DEFAULT_COMMAND_TOPIC
|
||||||
|
for entry_id, data in entries.items():
|
||||||
|
command_topic = data.command_topic
|
||||||
|
_LOGGER.info(
|
||||||
|
"Watering for %d seconds (%d ms) on topic %s (entry: %s)",
|
||||||
|
duration_seconds,
|
||||||
|
duration_ms,
|
||||||
|
command_topic,
|
||||||
|
entry_id,
|
||||||
|
)
|
||||||
|
await hass.services.async_call(
|
||||||
|
"mqtt",
|
||||||
|
"publish",
|
||||||
|
{
|
||||||
|
"topic": command_topic,
|
||||||
|
"payload": str(duration_ms),
|
||||||
|
"qos": 1,
|
||||||
|
"retain": False,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not entries:
|
||||||
|
_LOGGER.warning("No Greenhousino entries configured")
|
||||||
|
|
||||||
|
return async_water_for
|
||||||
|
|
||||||
|
|
||||||
|
async def _register_device(hass: HomeAssistant, entry: ConfigEntry, host: str):
|
||||||
|
"""Register the device in the device registry."""
|
||||||
|
dr.async_get(hass).async_get_or_create(
|
||||||
|
config_entry_id=entry.entry_id,
|
||||||
|
connections=set(),
|
||||||
|
identifiers={(DOMAIN, entry.entry_id)},
|
||||||
|
manufacturer=MANUFACTURER,
|
||||||
|
model=MODEL,
|
||||||
|
name=entry.title,
|
||||||
|
configuration_url=f"http://{host}" if host else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GreenhousinoData:
|
||||||
|
"""Container for integration data."""
|
||||||
|
|
||||||
|
def __init__(self, entry: ConfigEntry) -> None:
|
||||||
|
self.entry = entry
|
||||||
|
self.command_topic = entry.data.get(
|
||||||
|
CONF_COMMAND_TOPIC, DEFAULT_COMMAND_TOPIC
|
||||||
|
)
|
||||||
|
self.state_topic = entry.data.get(CONF_STATE_TOPIC, DEFAULT_STATE_TOPIC)
|
||||||
88
irrigation/homeassistant/greenhousino/config_flow.py
Normal file
88
irrigation/homeassistant/greenhousino/config_flow.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
"""Config flow for Greenhousino Irrigation integration."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
CONF_COMMAND_TOPIC,
|
||||||
|
CONF_HOST,
|
||||||
|
CONF_STATE_TOPIC,
|
||||||
|
DEFAULT_COMMAND_TOPIC,
|
||||||
|
DEFAULT_NAME,
|
||||||
|
DEFAULT_STATE_TOPIC,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
STEP_ONE_DATA_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_HOST, description={"suggested_value": ""}): str,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
STEP_TWO_DATA_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(
|
||||||
|
CONF_COMMAND_TOPIC,
|
||||||
|
description={"suggested_value": DEFAULT_COMMAND_TOPIC},
|
||||||
|
): str,
|
||||||
|
vol.Required(
|
||||||
|
CONF_STATE_TOPIC,
|
||||||
|
description={"suggested_value": DEFAULT_STATE_TOPIC},
|
||||||
|
): str,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GreenhousinoConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for Greenhousino Irrigation."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
async def async_step_user(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Handle the initial step (host)."""
|
||||||
|
errors: dict[str, str] = {}
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
try:
|
||||||
|
self._tmp_data = user_input
|
||||||
|
return await self.async_step_mqtt()
|
||||||
|
except Exception:
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user",
|
||||||
|
data_schema=STEP_ONE_DATA_SCHEMA,
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_mqtt(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Handle the MQTT topics step."""
|
||||||
|
errors: dict[str, str] = {}
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
try:
|
||||||
|
data = {**self._tmp_data, **user_input}
|
||||||
|
title = DEFAULT_NAME
|
||||||
|
if data.get(CONF_HOST):
|
||||||
|
title = f"{DEFAULT_NAME} ({data[CONF_HOST]})"
|
||||||
|
|
||||||
|
return self.async_create_entry(title=title, data=data)
|
||||||
|
except Exception:
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="mqtt",
|
||||||
|
data_schema=STEP_TWO_DATA_SCHEMA,
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
25
irrigation/homeassistant/greenhousino/const.py
Normal file
25
irrigation/homeassistant/greenhousino/const.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
"""Constants for Greenhousino Irrigation integration."""
|
||||||
|
|
||||||
|
DOMAIN = "greenhousino"
|
||||||
|
MANUFACTURER = "Greenhousino"
|
||||||
|
MODEL = "Irrigation Controller"
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
DEFAULT_NAME = "Greenhousino Irrigation"
|
||||||
|
DEFAULT_COMMAND_TOPIC = "greenhousino/pump"
|
||||||
|
DEFAULT_STATE_TOPIC = "greenhousino/pumpstate"
|
||||||
|
DEFAULT_PAYLOAD_ON = "on"
|
||||||
|
DEFAULT_PAYLOAD_OFF = "off"
|
||||||
|
|
||||||
|
# Config flow keys
|
||||||
|
CONF_COMMAND_TOPIC = "command_topic"
|
||||||
|
CONF_STATE_TOPIC = "state_topic"
|
||||||
|
CONF_HOST = "host"
|
||||||
|
|
||||||
|
# Service
|
||||||
|
SERVICE_WATER_FOR = "water_for"
|
||||||
|
SERVICE_DATA_DURATION = "duration"
|
||||||
|
|
||||||
|
# State
|
||||||
|
ATTR_DURATION_MS = "duration_ms"
|
||||||
|
ATTR_PUMP_ACTIVE = "pump_active"
|
||||||
14
irrigation/homeassistant/greenhousino/manifest.json
Normal file
14
irrigation/homeassistant/greenhousino/manifest.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"domain": "greenhousino",
|
||||||
|
"name": "Greenhousino Irrigation",
|
||||||
|
"codeowners": [],
|
||||||
|
"config_flow": true,
|
||||||
|
"dependencies": ["mqtt"],
|
||||||
|
"documentation": "https://github.com/greenhousino/irrigation",
|
||||||
|
"integration_type": "device",
|
||||||
|
"iot_class": "local_push",
|
||||||
|
"issue_tracker": "https://github.com/greenhousino/irrigation/issues",
|
||||||
|
"loggers": ["greenhousino"],
|
||||||
|
"requirements": [],
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
14
irrigation/homeassistant/greenhousino/services.yaml
Normal file
14
irrigation/homeassistant/greenhousino/services.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
water_for:
|
||||||
|
name: Water for duration
|
||||||
|
description: Activate the irrigation pump for a specified number of seconds.
|
||||||
|
fields:
|
||||||
|
duration:
|
||||||
|
name: Duration
|
||||||
|
description: How long to water, in seconds (1-3600).
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 1
|
||||||
|
max: 3600
|
||||||
|
unit_of_measurement: "seconds"
|
||||||
|
mode: box
|
||||||
39
irrigation/homeassistant/greenhousino/strings.json
Normal file
39
irrigation/homeassistant/greenhousino/strings.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Greenhousino Irrigation",
|
||||||
|
"description": "Enter the IP address of your ESP irrigation controller.",
|
||||||
|
"data": {
|
||||||
|
"host": "Host IP address"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mqtt": {
|
||||||
|
"title": "MQTT Topics",
|
||||||
|
"description": "Configure the MQTT topics for your irrigation controller.",
|
||||||
|
"data": {
|
||||||
|
"command_topic": "Command topic",
|
||||||
|
"state_topic": "State topic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"unknown": "An unknown error occurred."
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Device is already configured."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"water_for": {
|
||||||
|
"name": "Water for duration",
|
||||||
|
"description": "Activate the irrigation pump for a specified number of seconds.",
|
||||||
|
"fields": {
|
||||||
|
"duration": {
|
||||||
|
"name": "Duration",
|
||||||
|
"description": "How long to water, in seconds."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
151
irrigation/homeassistant/greenhousino/switch.py
Normal file
151
irrigation/homeassistant/greenhousino/switch.py
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
"""Switch platform for Greenhousino Irrigation."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.components import mqtt
|
||||||
|
from homeassistant.components.switch import SwitchEntity
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
ATTR_PUMP_ACTIVE,
|
||||||
|
DOMAIN,
|
||||||
|
MANUFACTURER,
|
||||||
|
MODEL,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up Greenhousino switch from config entry."""
|
||||||
|
hass_data = entry.runtime_data
|
||||||
|
|
||||||
|
async_add_entities(
|
||||||
|
[
|
||||||
|
GreenhousinoPumpSwitch(
|
||||||
|
entry,
|
||||||
|
hass_data.command_topic,
|
||||||
|
hass_data.state_topic,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GreenhousinoPumpSwitch(SwitchEntity):
|
||||||
|
"""Representation of a Greenhousino pump switch."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
_attr_name = "Pump"
|
||||||
|
_attr_should_poll = False
|
||||||
|
_attr_unique_id = None # will be set from entry_id
|
||||||
|
_entry_id = None
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
command_topic: str,
|
||||||
|
state_topic: str,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the switch."""
|
||||||
|
self._command_topic = command_topic
|
||||||
|
self._state_topic = state_topic
|
||||||
|
self._entry_id = entry.entry_id
|
||||||
|
self._attr_unique_id = f"{entry.entry_id}-pump"
|
||||||
|
self._attr_available = False
|
||||||
|
self._state = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return device info."""
|
||||||
|
return {
|
||||||
|
"identifiers": {(DOMAIN, self._entry_id)},
|
||||||
|
"manufacturer": MANUFACTURER,
|
||||||
|
"model": MODEL,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def async_added_to_hass(self) -> None:
|
||||||
|
"""Register callbacks."""
|
||||||
|
entry = self.config_entry
|
||||||
|
|
||||||
|
self.async_on_remove(
|
||||||
|
mqtt.async_subscribe(
|
||||||
|
self.hass,
|
||||||
|
self._state_topic,
|
||||||
|
self._state_received,
|
||||||
|
qos=1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Publish availability via MQTT discovery-style
|
||||||
|
# The device publishes to greenhousino/pump/status with "offline" on disconnect
|
||||||
|
status_topic = self._state_topic.replace("pumpstate", "pump/status")
|
||||||
|
self.async_on_remove(
|
||||||
|
mqtt.async_subscribe(
|
||||||
|
self.hass,
|
||||||
|
status_topic,
|
||||||
|
self._availability_received,
|
||||||
|
qos=1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _state_received(self, msg: mqtt.MqttReceivePayload) -> None:
|
||||||
|
"""Handle new MQTT state message."""
|
||||||
|
payload = msg.decode().strip()
|
||||||
|
self._state = payload == "on"
|
||||||
|
self._attr_available = True
|
||||||
|
self.async_write_ha_state()
|
||||||
|
_LOGGER.debug("State received: %s -> pump_active=%s", payload, self._state)
|
||||||
|
|
||||||
|
def _availability_received(self, msg: mqtt.MqttReceivePayload) -> None:
|
||||||
|
"""Handle availability message."""
|
||||||
|
payload = msg.decode().strip()
|
||||||
|
self._attr_available = payload != "offline"
|
||||||
|
self.async_write_ha_state()
|
||||||
|
_LOGGER.debug("Availability: %s", payload)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool:
|
||||||
|
"""Return True if the pump is on."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
|
"""Return device state attributes."""
|
||||||
|
return {
|
||||||
|
ATTR_PUMP_ACTIVE: self._state,
|
||||||
|
"command_topic": self._command_topic,
|
||||||
|
"state_topic": self._state_topic,
|
||||||
|
"manufacturer": MANUFACTURER,
|
||||||
|
"model": MODEL,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the pump on."""
|
||||||
|
await mqtt.async_publish(
|
||||||
|
self.hass,
|
||||||
|
self._command_topic,
|
||||||
|
"on",
|
||||||
|
qos=1,
|
||||||
|
retain=False,
|
||||||
|
)
|
||||||
|
self._state = True
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
async def async_turn_off(self) -> None:
|
||||||
|
"""Turn the pump off."""
|
||||||
|
await mqtt.async_publish(
|
||||||
|
self.hass,
|
||||||
|
self._command_topic,
|
||||||
|
"off",
|
||||||
|
qos=1,
|
||||||
|
retain=False,
|
||||||
|
)
|
||||||
|
self._state = False
|
||||||
|
self.async_write_ha_state()
|
||||||
39
irrigation/homeassistant/greenhousino/translations/en.json
Normal file
39
irrigation/homeassistant/greenhousino/translations/en.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Greenhousino Irrigation",
|
||||||
|
"description": "Enter the IP address of your ESP irrigation controller.",
|
||||||
|
"data": {
|
||||||
|
"host": "Host IP address"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mqtt": {
|
||||||
|
"title": "MQTT Topics",
|
||||||
|
"description": "Configure the MQTT topics for your irrigation controller.",
|
||||||
|
"data": {
|
||||||
|
"command_topic": "Command topic",
|
||||||
|
"state_topic": "State topic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"unknown": "An unknown error occurred."
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Device is already configured."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"water_for": {
|
||||||
|
"name": "Water for duration",
|
||||||
|
"description": "Activate the irrigation pump for a specified number of seconds.",
|
||||||
|
"fields": {
|
||||||
|
"duration": {
|
||||||
|
"name": "Duration",
|
||||||
|
"description": "How long to water, in seconds."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -126,7 +126,8 @@ void reconnect()
|
|||||||
{
|
{
|
||||||
Serial.println("INFO: Attempting MQTT connection...");
|
Serial.println("INFO: Attempting MQTT connection...");
|
||||||
// Attempt to connect
|
// Attempt to connect
|
||||||
if (mqttClient.connect(MQTT_CLIENT_ID, MQTT_CLIENT_USER, MQTT_CLIENT_PW))
|
if (mqttClient.connect(MQTT_CLIENT_ID, MQTT_CLIENT_USER, MQTT_CLIENT_PW,
|
||||||
|
MQTT_LAST_WILL_TOPIC, 1, true, MQTT_LAST_WILL_MSG))
|
||||||
{
|
{
|
||||||
Serial.println("INFO: connected");
|
Serial.println("INFO: connected");
|
||||||
|
|
||||||
@@ -136,6 +137,9 @@ void reconnect()
|
|||||||
mqttClient.publish(MQTT_STATE_TOPIC, MQTT_OFF);
|
mqttClient.publish(MQTT_STATE_TOPIC, MQTT_OFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Signal online to Home Assistant availability tracker
|
||||||
|
mqttClient.publish(MQTT_LAST_WILL_TOPIC, "online");
|
||||||
|
|
||||||
mqttClient.subscribe(MQTT_TF_TOPIC);
|
mqttClient.subscribe(MQTT_TF_TOPIC);
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user