Skip to content

Commit 3959adf

Browse files
committed
Ready for release
1 parent b8ffa37 commit 3959adf

File tree

10 files changed

+401
-37
lines changed

10 files changed

+401
-37
lines changed

LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Vegetronix
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,36 @@
11
# VegeHub on HACS
22

3-
This is a Home Assistant integration designed to interface with the Vegetronix VegeHub. The version in this repository is intended to be accessed through HACS, but can also be manually installed into the `custom_components` folder of your Home Assistant server if desired.
3+
[![vegetronix_badge](https://img.shields.io/badge/VEGETRONIX-VEGEHUB-green)](https://www.vegetronix.com/Products/VG-HUB-RELAY/)
4+
5+
This is a [Home Assistant](https://www.home-assistant.io/) integration designed to interface with the [Vegetronix VegeHub](https://www.vegetronix.com/Products/VG-HUB-RELAY/). The version in this repository is intended to be accessed through [HACS](https://hacs.xyz/), but can also be manually installed into the `custom_components` folder of your Home Assistant server if desired.
6+
7+
This integration allows Home Assistant to automatically detect the presence of VegeHub devices on the local network, and to set them up to report their data to Home Assistant. It also allows Home Assistant to control actuators on the VegeHub (as long as the VegeHub is awake).
8+
9+
## Installation
10+
11+
### HACS
12+
13+
The easiest way to install this integration is through the Home Assistant Community Store [HACS](https://hacs.xyz/). Instructions for installing HACS can be found at their [website](https://hacs.xyz/docs/use/).
14+
15+
Once HACS is installed, open it in Home Assistant, and search for VegeHub. Click the VegeHub integration, and then click `install`.
16+
17+
If the integration does not show up in a HACS search, you can also add this repository as a `Custom repository` buy clicking the three dots in the top right corner of HACS and selecting `Custom repositories`. There you can add the URL of this repository and set the `type` to `integration`, and HACS will add it to its list of integrations.
18+
19+
### Manual
20+
21+
The integration can also be manually by copying the `vegehub` folder from the `custom_components` folder in this project into the `config->custom_components` folder in your Home Assistant instance. After the folder is copied into Home Assistant, Home Assistant should be restarted so that it can install the integration.
22+
23+
## Instructions
24+
25+
Once you have installed the integration, Home Assistant will start watching the local network for VegeHub devices. Whe it identifies one, it will present it in the `Discovered` devices in the `Settings->Devices & Services` menu. Click `Add`, and Home Assistant will contact the VegeHub, and set it up to report its data to Home Assistant.
26+
27+
If your VegeHub does not show up in Home Assistant, make sure the device is awake, and connected to the same network as Home Assistant. Also make sure that the VegeHub device is awake throughout the integration setup process.
28+
29+
> [!IMPORTANT]
30+
> When the VegeHub gets set up, it points its data updates at your Home Assistant's IP address. If your Home Assistant instance changes IP address, the VegeHub will no longer be able to send it data, and you will have to do the setup process over again.
31+
32+
Once setup is complete, the VegeHub device will be available in Home Assistant. If your device has actuators, they will show up as switches, and if your device has sensor inputs, they will show up as sensors.
33+
34+
With a configured device in Home Assistant, you can go into the `Settings->Devices & Services` menu, and click on the VegeHub integration. With that open, you should see your device listed under `Integration entries`. Here you can click `configure` to select what kinds of sensors you are using on each channel of your VegeHub, as well as the default actuator command duration, if your device has actuators.
35+
36+
If you click your device under `Integration entries`, you can use the pencil icon in the top bar to change its name, or click the individual sensors or switches to change their names, icons, units of measurement, etc.

custom_components/vegehub/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from aiohttp.hdrs import METH_POST
88
from aiohttp.web import Request, Response
9-
from vegehub import VegeHub
109

1110
from homeassistant.components.http import HomeAssistantView
1211
from homeassistant.components.webhook import (
@@ -21,6 +20,7 @@
2120
EVENT_HOMEASSISTANT_STOP,
2221
)
2322
from homeassistant.core import HomeAssistant
23+
from vegehub import VegeHub
2424

2525
from .const import DOMAIN, NAME, PLATFORMS
2626
from .coordinator import VegeHubConfigEntry, VegeHubCoordinator
@@ -50,6 +50,8 @@ async def unregister_webhook(_: Any) -> None:
5050

5151
async def register_webhook() -> None:
5252
webhook_name = f"{NAME} {device_mac}"
53+
if entry.data[CONF_WEBHOOK_ID] in hass.data.get("webhook", {}):
54+
webhook_unregister(hass, entry.data[CONF_WEBHOOK_ID])
5355

5456
webhook_register(
5557
hass,

custom_components/vegehub/config_flow.py

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,31 @@
33
import logging
44
from typing import Any
55

6-
from vegehub import VegeHub
76
import voluptuous as vol
87

98
from homeassistant.components.webhook import (
109
async_generate_id as webhook_generate_id,
1110
async_generate_url as webhook_generate_url,
1211
)
13-
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
12+
from homeassistant.config_entries import (
13+
ConfigEntry,
14+
ConfigFlow,
15+
ConfigFlowResult,
16+
OptionsFlow,
17+
)
1418
from homeassistant.const import (
1519
CONF_DEVICE,
1620
CONF_HOST,
1721
CONF_IP_ADDRESS,
1822
CONF_MAC,
1923
CONF_WEBHOOK_ID,
2024
)
25+
from homeassistant.core import callback
2126
from homeassistant.helpers.service_info import zeroconf
2227
from homeassistant.util.network import is_ip_address
28+
from vegehub import VegeHub
2329

24-
from .const import DOMAIN
30+
from .const import DOMAIN, OPTION_DATA_TYPE_CHOICES
2531

2632
_LOGGER = logging.getLogger(__name__)
2733

@@ -166,3 +172,72 @@ async def _create_entry(self) -> ConfigFlowResult:
166172

167173
# Create the config entry for the new device
168174
return self.async_create_entry(title=self._hostname, data=info_data)
175+
176+
@staticmethod
177+
@callback
178+
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
179+
"""Return the options flow handler for this integration."""
180+
return VegehubOptionsFlowHandler(config_entry)
181+
182+
183+
class VegehubOptionsFlowHandler(OptionsFlow):
184+
"""Handle an options flow for VegeHub."""
185+
186+
def __init__(self, config_entry) -> None:
187+
"""Initialize VegeHub options flow."""
188+
self.coordinator = config_entry.runtime_data
189+
190+
async def async_step_init(self, user_input=None) -> ConfigFlowResult:
191+
"""Manage the options for VegeHub."""
192+
if user_input is not None:
193+
# Update the config entry options with the new user input
194+
self.hass.config_entries.async_update_entry(
195+
self.config_entry, options=user_input
196+
)
197+
198+
# Trigger a reload of the config entry to apply the new options
199+
await self.hass.config_entries.async_reload(self.config_entry.entry_id)
200+
201+
# Process the user inputs and update the config entry options
202+
return self.async_create_entry(title="", data=user_input)
203+
204+
num_sensors = self.coordinator.vegehub.num_sensors
205+
num_actuators = self.coordinator.vegehub.num_actuators
206+
207+
options_schema: dict[Any, Any] = {}
208+
209+
if num_sensors > 0:
210+
# Define the schema for the options that the user can modify
211+
options_schema.update(
212+
{
213+
vol.Required(
214+
f"data_type_{i + 1}",
215+
default=self.config_entry.options.get(
216+
f"data_type_{i + 1}", OPTION_DATA_TYPE_CHOICES[0]
217+
),
218+
): vol.In(OPTION_DATA_TYPE_CHOICES)
219+
for i in range(num_sensors)
220+
}
221+
)
222+
223+
# Check to see if there are actuators. If there are, add the duration field.
224+
if num_actuators > 0:
225+
# Get the current duration value from the config entry
226+
current_duration = self.config_entry.options.get("user_act_duration", 0)
227+
if current_duration <= 0:
228+
current_duration = 600
229+
230+
options_schema.update(
231+
{vol.Required("user_act_duration", default=current_duration): int}
232+
)
233+
234+
_LOGGER.debug(
235+
# Print the options schema to the log for debugging
236+
"Options schema: %s",
237+
options_schema,
238+
)
239+
240+
# Show the form to the user with the current options
241+
return self.async_show_form(
242+
step_id="init", data_schema=vol.Schema(options_schema)
243+
)

custom_components/vegehub/const.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55
DOMAIN = "vegehub"
66
NAME = "VegeHub"
7-
PLATFORMS = [Platform.SENSOR]
7+
PLATFORMS = [Platform.SENSOR, Platform.SWITCH]
88
MANUFACTURER = "vegetronix"
99
MODEL = "VegeHub"
10+
OPTION_DATA_TYPE_CHOICES = [
11+
"Raw Voltage",
12+
"VH400",
13+
"THERM200",
14+
]

custom_components/vegehub/coordinator.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55
import logging
66
from typing import Any
77

8-
from vegehub import VegeHub, update_data_to_latest_dict
9-
108
from homeassistant.config_entries import ConfigEntry
119
from homeassistant.core import HomeAssistant
1210
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
11+
from vegehub import VegeHub, update_data_to_latest_dict
1312

1413
_LOGGER = logging.getLogger(__name__)
1514

custom_components/vegehub/sensor.py

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,46 @@
11
"""Sensor configuration for VegeHub integration."""
22

33
from itertools import count
4+
import logging
45

56
from homeassistant.components.sensor import (
67
SensorDeviceClass,
78
SensorEntity,
89
SensorEntityDescription,
910
)
10-
from homeassistant.const import UnitOfElectricPotential
11+
from homeassistant.const import PERCENTAGE, UnitOfElectricPotential, UnitOfTemperature
1112
from homeassistant.core import HomeAssistant
1213
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
14+
from vegehub import therm200_transform, vh400_transform
1315

16+
from .const import OPTION_DATA_TYPE_CHOICES
1417
from .coordinator import VegeHubConfigEntry, VegeHubCoordinator
1518
from .entity import VegeHubEntity
1619

20+
_LOGGING = logging.getLogger(__name__)
21+
1722
SENSOR_TYPES: dict[str, SensorEntityDescription] = {
18-
"analog_sensor": SensorEntityDescription(
23+
OPTION_DATA_TYPE_CHOICES[0]: SensorEntityDescription(
1924
key="analog_sensor",
2025
translation_key="analog_sensor",
2126
device_class=SensorDeviceClass.VOLTAGE,
2227
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
2328
suggested_display_precision=2,
2429
),
30+
OPTION_DATA_TYPE_CHOICES[1]: SensorEntityDescription(
31+
key="vh400_sensor",
32+
translation_key="vh400_sensor",
33+
device_class=SensorDeviceClass.MOISTURE,
34+
native_unit_of_measurement=PERCENTAGE,
35+
suggested_display_precision=2,
36+
),
37+
OPTION_DATA_TYPE_CHOICES[2]: SensorEntityDescription(
38+
key="therm200_sensor",
39+
translation_key="therm200_sensor",
40+
device_class=SensorDeviceClass.TEMPERATURE,
41+
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
42+
suggested_display_precision=2,
43+
),
2544
"battery": SensorEntityDescription(
2645
key="battery",
2746
translation_key="battery",
@@ -45,12 +64,24 @@ async def async_setup_entry(
4564
# each sensor. This index is 1-based.
4665
update_index = count(1)
4766

67+
_LOGGING.debug("Options contents: %s", config_entry.options)
68+
4869
# Add each analog sensor input
4970
for _i in range(coordinator.vegehub.num_sensors):
71+
index = next(update_index)
72+
data_type = config_entry.options.get(
73+
f"data_type_{index}", OPTION_DATA_TYPE_CHOICES[0]
74+
)
75+
_LOGGING.debug(
76+
"Sensor %s: %s = %s",
77+
_i,
78+
f"data_type_{index}",
79+
data_type,
80+
)
5081
sensor = VegeHubSensor(
51-
index=next(update_index),
82+
index=index,
5283
coordinator=coordinator,
53-
description=SENSOR_TYPES["analog_sensor"],
84+
description=SENSOR_TYPES[data_type],
5485
)
5586
sensors.append(sensor)
5687

@@ -69,8 +100,6 @@ async def async_setup_entry(
69100
class VegeHubSensor(VegeHubEntity, SensorEntity):
70101
"""Class for VegeHub Analog Sensors."""
71102

72-
_attr_device_class = SensorDeviceClass.VOLTAGE
73-
_attr_native_unit_of_measurement = UnitOfElectricPotential.VOLT
74103
_attr_suggested_display_precision = 2
75104

76105
def __init__(
@@ -84,13 +113,28 @@ def __init__(
84113
self.entity_description = description
85114
# Set unique ID for pulling data from the coordinator
86115
self._attr_unique_id = f"{self._mac_address}_{index}".lower()
87-
if description.key == "analog_sensor":
116+
if description.key != "battery":
88117
self._attr_translation_placeholders = {"index": str(index)}
89118
self._attr_available = False
119+
self._attr_device_class = description.device_class
120+
self._attr_native_unit_of_measurement = description.native_unit_of_measurement
90121

91122
@property
92123
def native_value(self) -> float | None:
93124
"""Return the sensor's current value."""
94125
if self.coordinator.data is None or self._attr_unique_id is None:
95126
return None
96-
return self.coordinator.data.get(self._attr_unique_id)
127+
val = self.coordinator.data.get(self._attr_unique_id)
128+
_LOGGING.debug(
129+
"Sensor %s: %s = %s",
130+
self._attr_unique_id,
131+
self.entity_description.key,
132+
val,
133+
)
134+
if self.entity_description.key == "vh400_sensor":
135+
# Convert the vh400 sensor value to percentage
136+
return vh400_transform(val)
137+
if self.entity_description.key == "therm200_sensor":
138+
# Convert the therm200 sensor value to Celsius
139+
return therm200_transform(val)
140+
return val

custom_components/vegehub/strings.json

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
}
1515
},
1616
"zeroconf_confirm": {
17-
"title": "[%key:component::vegehub::config::step::user::title%]",
18-
"description": "[%key:component::vegehub::config::step::user::description%]"
17+
"title": "Set up VegeHub",
18+
"description": "Do you want to set up this VegeHub?"
1919
}
2020
},
2121
"error": {
@@ -36,9 +36,49 @@
3636
"analog_sensor": {
3737
"name": "Input {index}"
3838
},
39+
"vh400_sensor": {
40+
"name": "Input {index} (moisture)"
41+
},
42+
"therm200_sensor": {
43+
"name": "Input {index} (temperature)"
44+
},
3945
"battery": {
4046
"name": "Battery"
4147
}
48+
},
49+
"switch": {
50+
"switch": {
51+
"name": "Actuator {index}"
52+
}
53+
}
54+
},
55+
"options": {
56+
"step": {
57+
"init": {
58+
"title": "Configure VegeHub",
59+
"data": {
60+
"user_act_duration": "Default actuator duration (in seconds)",
61+
"data_type_1": "Input 1 sensor type",
62+
"data_type_2": "Input 2 sensor type",
63+
"data_type_3": "Input 3 sensor type",
64+
"data_type_4": "Input 4 sensor type",
65+
"data_type_5": "Input 5 sensor type",
66+
"data_type_6": "Input 6 sensor type",
67+
"data_type_7": "Input 7 sensor type",
68+
"data_type_8": "Input 8 sensor type"
69+
},
70+
"data_description": {
71+
"user_act_duration": "Length of time that an actuator command will endure",
72+
"data_type_1": "Select the type of sensor connected to input 1",
73+
"data_type_2": "Select the type of sensor connected to input 2",
74+
"data_type_3": "Select the type of sensor connected to input 3",
75+
"data_type_4": "Select the type of sensor connected to input 4",
76+
"data_type_5": "Select the type of sensor connected to input 5",
77+
"data_type_6": "Select the type of sensor connected to input 6",
78+
"data_type_7": "Select the type of sensor connected to input 7",
79+
"data_type_8": "Select the type of sensor connected to input 8"
80+
}
81+
}
4282
}
4383
}
44-
}
84+
}

0 commit comments

Comments
 (0)