Skip to content

Commit

Permalink
Merge pull request #75 from mion00/feat/mqtt_input_text
Browse files Browse the repository at this point in the history
feat: MQTT text entity
  • Loading branch information
unixorn authored May 30, 2023
2 parents aa06987 + fb47a50 commit 7a89f8a
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 4 deletions.
48 changes: 44 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ Using MQTT discoverable devices lets us add new sensors and devices to HA withou
- [Usage](#usage)
- [Switch](#switch)
- [Usage](#usage-1)
- [Text](#text)
- [Usage](#usage-2)
- [Device](#device)
- [Usage](#usage-2)
- [Usage](#usage-3)
- [Device trigger](#device-trigger)
- [Usage](#usage-3)
- [Usage](#usage-4)
- [Contributing](#contributing)
- [Users of ha-mqtt-discoverable](#users-of-ha-mqtt-discoverable)
- [Contributors](#contributors)
Expand All @@ -48,6 +50,8 @@ The following Home Assistant entities are currently implemented:
- Button
- Device trigger

Each entity can associated to a device. See below for details.

### Binary sensor

#### Usage
Expand Down Expand Up @@ -95,8 +99,7 @@ from paho.mqtt.client import Client, MQTTMessage
mqtt_settings = Settings.MQTT(host="localhost")

# Information about the switch
# If `command_topic` is defined, it will receive state updates from HA
switch_info = SwitchInfo(name="test", command_topic="command")
switch_info = SwitchInfo(name="test")

settings = Settings(mqtt=mqtt_settings, entity=switch_info)

Expand All @@ -118,6 +121,43 @@ my_switch.off()

```

### Text

The text is an `helper entity`, showing an input field in the HA UI that the user can interact with.
It is possible to act upon reception of the inputted text by defining a `callback` function, as the following example shows:

#### Usage

```py
from ha_mqtt_discoverable import Settings
from ha_mqtt_discoverable.sensors import Text, TextInfo
from paho.mqtt.client import Client, MQTTMessage

# Configure the required parameters for the MQTT broker
mqtt_settings = Settings.MQTT(host="localhost")

# Information about the `text` entity
text_info = TextInfo(name="test")

settings = Settings(mqtt=mqtt_settings, entity=switch_info)

# To receive text updates from HA, define a callback function:
def my_callback(client: Client, user_data, message: MQTTMessage):
text = message.payload.decode()
logging.info(f"Received {text} from HA")
# Your custom code...

# Define an optional object to be passed back to the callback
user_data = "Some custom data"

# Instantiate the text
my_text = Text(settings, my_callback, user_data)

# Change the text displayed in HA UI, publishing an MQTT message that gets picked up by HA
my_text.set_text("Some awesome text")

```

## Device
From the [Home Assistant documentation](https://developers.home-assistant.io/docs/device_registry_index):
> A device is a special entity in Home Assistant that is represented by one or more entities.
Expand Down
2 changes: 2 additions & 0 deletions ha_mqtt_discoverable/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,8 @@ class EntityInfo(BaseModel):
"""Sets the class of the device, changing the device state and icon that is displayed on the frontend."""
enabled_by_default: Optional[bool] = None
"""Flag which defines if the entity should be enabled when first added."""
entity_category: Optional[str] = None
"""Classification of a non-primary entity."""
expire_after: Optional[int] = None
"""If set, it defines the number of seconds after the sensor’s state expires, if it’s not updated.\
After expiry, the sensor’s state becomes unavailable. Default the sensors state never expires."""
Expand Down
39 changes: 39 additions & 0 deletions ha_mqtt_discoverable/sensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,24 @@ class ButtonInfo(EntityInfo):
"""If the published message should have the retain flag on or not"""


class TextInfo(EntityInfo):
"""Information about the `text` entity"""

component: str = "text"

max: int = 255
"""The maximum size of a text being set or received (maximum is 255)."""
min: int = 0
"""The minimum size of a text being set or received."""
mode: Optional[str] = "text"
"""The mode off the text entity. Must be either text or password."""
pattern: Optional[str] = None
"""A valid regular expression the text being set or received must match with."""

retain: Optional[bool] = None
"""If the published message should have the retain flag on or not"""


class DeviceTriggerInfo(EntityInfo):
"""Information about the device trigger"""

Expand Down Expand Up @@ -190,3 +208,24 @@ def trigger(self, payload: Optional[str] = None):
"""
return self._state_helper(payload, self.state_topic, retain=False)


class Text(Subscriber[TextInfo]):
"""Implements an MQTT text:
https://www.home-assistant.io/integrations/text.mqtt/
"""

def set_text(self, text: str) -> None:
"""
Update the text displayed by this sensor. Check that it is of acceptable length.
Args:
text(str): Value of the text configured for this entity
"""
if not self._entity.min <= len(text) <= self._entity.max:
raise RuntimeError(
f"Text is not within configured length boundaries [{self._entity.min}, {self._entity.max}]"
)

logger.info(f"Setting {self._entity.name} to {text} using {self.state_topic}")
self._state_helper(str(text))
40 changes: 40 additions & 0 deletions tests/test_text.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import random
import string
import pytest
from ha_mqtt_discoverable import Settings
from ha_mqtt_discoverable.sensors import Text, TextInfo


@pytest.fixture()
def text() -> Text:
mqtt_settings = Settings.MQTT(host="localhost")
text_info = TextInfo(name="test", min=5)
settings = Settings(mqtt=mqtt_settings, entity=text_info)
# Define empty callback
return Text(settings, lambda *_: None)


def test_required_config():
mqtt_settings = Settings.MQTT(host="localhost")
text_info = TextInfo(name="test")
settings = Settings(mqtt=mqtt_settings, entity=text_info)
# Define empty callback
text = Text(settings, lambda *_: None)
assert text is not None


def test_set_text(text: Text):
text.set_text("this is as test")


def test_too_short_string(text: Text):
with pytest.raises(RuntimeError):
text.set_text("t")


def test_too_long_string(text: Text):
length = 500
letters = string.ascii_lowercase
random_string = "".join(random.choice(letters) for i in range(length))
with pytest.raises(RuntimeError):
text.set_text(random_string)

0 comments on commit 7a89f8a

Please sign in to comment.