Skip to content

Commit

Permalink
Merge pull request #140 from ha-enthus1ast/feature/light
Browse files Browse the repository at this point in the history
Feature: light entity
  • Loading branch information
unixorn authored Nov 22, 2023
2 parents 9ba66c8 + 7cba22a commit d7db4d6
Show file tree
Hide file tree
Showing 5 changed files with 393 additions and 70 deletions.
75 changes: 73 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ Using MQTT discoverable devices lets us add new sensors and devices to HA withou
- [Usage](#usage-2)
- [Switch](#switch)
- [Usage](#usage-3)
- [Text](#text)
- [Light](#light)
- [Usage](#usage-4)
- [Number](#number)
- [Text](#text)
- [Usage](#usage-5)
- [Number](#number)
- [Usage](#usage-6)
- [Contributing](#contributing)
- [Users of ha-mqtt-discoverable](#users-of-ha-mqtt-discoverable)
- [Contributors](#contributors)
Expand Down Expand Up @@ -240,6 +242,75 @@ my_switch.off()

```

### Light

The light is different from other current sensor as it needs its payload encoded/decoded as json.
It is possible to set brightness, effects and the color of the light. Similar to a _switch_ it can
also receive 'commands' from HA that request a state change.
It is possible to act upon reception of this 'command', by defining a `callback` function, as the following example shows:

#### Usage

```py
import json
from ha_mqtt_discoverable import Settings
from ha_mqtt_discoverable.sensors import Light, LightInfo
from paho.mqtt.client import Client, MQTTMessage

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

# Information about the light
light_info = LightInfo(
name="test_light",
brightness=True,
color_mode=True,
supported_color_modes=["rgb"],
effect=True,
effect_list=["blink", "my_cusom_effect"])

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

# To receive state commands from HA, define a callback function:
def my_callback(client: Client, user_data, message: MQTTMessage):

# Make sure received payload is json
try:
payload = json.loads(message.payload.decode())
except ValueError as error:
print("Ony JSON schema is supported for light entities!")
return

# Parse received dictionary
if "color" in payload:
set_color_of_my_light()
my_light.color("rgb", payload["color"])
elif "brightness" in payload:
set_brightness_of_my_light()
my_light.brightness(payload["brightness"])
elif "effect" in payload:
set_effect_of_my_light()
my_light.effect(payload["effect"])
elif "state" in payload:
if payload["state"] == light_info.payload_on:
turn_on_my_light()
my_light.on()
else:
turn_off_my_light()
my_light.off()
else:
print("Unknown payload")

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

# Instantiate the switch
my_light = Light(settings, my_callback, user_data)

# Set the initial state of the light, which also makes it discoverable
my_light.off()

```

### Text

Expand Down
2 changes: 1 addition & 1 deletion ha_mqtt_discoverable/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,7 @@ def generate_config(self) -> dict[str, Any]:
automagically ingest the new sensor.
"""
# Automatically generate a dict using pydantic
config = self._entity.dict(exclude_none=True)
config = self._entity.dict(exclude_none=True, by_alias=True)
# Add the MQTT topics to be discovered by HA
topics = {
"state_topic": self.state_topic,
Expand Down
142 changes: 142 additions & 0 deletions ha_mqtt_discoverable/sensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# Required to define a class itself as type https://stackoverflow.com/a/33533514
from __future__ import annotations

import json
import logging
from typing import Any, Optional

Expand All @@ -25,6 +26,7 @@
EntityInfo,
Subscriber,
)
from pydantic import Field

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -77,6 +79,45 @@ class SwitchInfo(EntityInfo):
"""The MQTT topic subscribed to receive state updates."""


class LightInfo(EntityInfo):
"""Light specific information"""

component: str = "light"

state_schema: str = Field(
default="json", alias="schema"
) # 'schema' is a reserved word by pydantic
"""Sets the schema of the state topic, ie the 'schema' field in the configuration"""
optimistic: Optional[bool] = None
"""Flag that defines if light works in optimistic mode.
Default: true if no state_topic defined, else false."""
payload_off: str = "OFF"
"""The payload that represents off state. If specified, will be used for
both comparing to the value in the state_topic (see value_template and
state_off for details) and sending as off command to the command_topic"""
payload_on: str = "ON"
"""The payload that represents on state. If specified, will be used for both
comparing to the value in the state_topic (see value_template and state_on
for details) and sending as on command to the command_topic."""
brightness: Optional[bool] = False
"""Flag that defines if the light supports setting the brightness
"""
color_mode: Optional[bool] = False
"""Flag that defines if the light supports color mode"""
supported_color_modes: Optional[list[str]] = None
"""List of supported color modes. See
https://www.home-assistant.io/integrations/light.mqtt/#supported_color_modes for current list of
supported modes. Required if color_mode is set"""
effect: Optional[bool] = False
"""Flag that defines if the light supports effects"""
effect_list: Optional[str | list] = None
"""List of supported effects. Required if effect is set"""
retain: Optional[bool] = True
"""If the published message should have the retain flag on or not"""
state_topic: Optional[str] = None
"""The MQTT topic subscribed to receive state updates."""


class ButtonInfo(EntityInfo):
"""Button specific information"""

Expand Down Expand Up @@ -214,6 +255,107 @@ def on(self):
super().on()


class Light(Subscriber[LightInfo]):
"""Implements an MQTT light.
https://www.home-assistant.io/integrations/light.mqtt
"""

def on(self) -> None:
"""
Set light to on
"""
state_payload = {
"state": self._entity.payload_on,
}
self._update_state(state_payload)

def off(self) -> None:
"""
Set light to off
"""
state_payload = {
"state": self._entity.payload_off,
}
self._update_state(state_payload)

def brightness(self, brightness: int) -> None:
"""
Set brightness of the light
Args:
brightness(int): Brightness value of [0,255]
"""
if brightness < 0 or brightness > 255:
raise RuntimeError(
f"Brightness for light {self._entity.name} is out of range"
)

state_payload = {
"brightness": brightness,
"state": self._entity.payload_on,
}

self._update_state(state_payload)

def color(self, color_mode: str, color: dict[str, Any]) -> None:
"""
Set color of the light.
NOTE: Make sure color formatting conforms to color mode, it is up to the caller to make sure
of this. Also, make sure the color mode is in the list supported_color_modes
Args:
color_mode(str): A valid color mode
color(Dict[str, Any]): Color to set, according to color_mode format
"""
if not self._entity.color_mode:
raise RuntimeError(
f"Light {self._entity.name} does not support setting color"
)
if color_mode not in self._entity.supported_color_modes:
raise RuntimeError(
f"Color is not in configured supported_color_modes {str(self._entity.supported_color_modes)}"
)
# We do not check if color schema conforms to color mode formatting, it is up to the caller
state_payload = {
"color_mode": color_mode,
"color": color,
"state": self._entity.payload_on,
}
self._update_state(state_payload)

def effect(self, effect: str) -> None:
"""
Enable effect of the light
Args:
effect(str): Effect to apply
"""
if not self._entity.effect:
raise RuntimeError(f"Light {self._entity.name} does not support effects")
if effect not in self._entity.effect_list:
raise RuntimeError(
f"Effect is not within configured effect_list {str(self._entity.effect_list)}"
)
state_payload = {
"effect": effect,
"state": self._entity.payload_on,
}
self._update_state(state_payload)

def _update_state(self, state: dict[str, Any]) -> None:
"""
Update MQTT sensor state
Args:
state(Dict[str, Any]): What state to set the light to
"""
logger.info(f"Setting {self._entity.name} to {state} using {self.state_topic}")
json_state = json.dumps(state)
self._state_helper(
state=json_state, topic=self.state_topic, retain=self._entity.retain
)


class Button(Subscriber[ButtonInfo]):
"""Implements an MQTT button:
https://www.home-assistant.io/integrations/button.mqtt
Expand Down
Loading

0 comments on commit d7db4d6

Please sign in to comment.