Skip to content

Commit 4913f2e

Browse files
committed
Adding support for fans
1 parent 97c8685 commit 4913f2e

File tree

4 files changed

+171
-1
lines changed

4 files changed

+171
-1
lines changed

examples/sample_fan.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/env python3
2+
"""Example of the most simple usage of a Fan entity.
3+
4+
The Fan has five speeds.
5+
"""
6+
7+
from ham import MqttManager
8+
from ham.fan import PercentageOptimisticFan
9+
from time import sleep
10+
import os
11+
12+
MQTT_USERNAME = os.environ["MQTT_USERNAME"]
13+
MQTT_PASSWORD = os.environ["MQTT_PASSWORD"]
14+
MQTT_HOST = os.environ["MQTT_HOST"]
15+
16+
17+
class SampleFan(PercentageOptimisticFan):
18+
name = "Awesomest Fan"
19+
short_id = "moreawesome"
20+
21+
speed_range_min = 1
22+
speed_range_max = 5
23+
24+
def callback(self, state: bool):
25+
super().callback(state)
26+
print("The fan is set to %s" % ("on" if state else "off",))
27+
28+
def speed_callback(self, speed: int):
29+
super().speed_callback(speed)
30+
print("The fan is set to %s (received: %s)" % (self.speed, speed))
31+
32+
def set_speed(self, speed: int):
33+
print("Setting value. Previous state: %d. New state: %d" % (self.speed, speed))
34+
self.speed = speed
35+
36+
37+
if __name__ == "__main__":
38+
main_fan = SampleFan()
39+
manager = MqttManager(MQTT_HOST, username=MQTT_USERNAME, password=MQTT_PASSWORD)
40+
manager.add_thing(main_fan)
41+
42+
manager.start()
43+
44+
a = 1
45+
print("Entering an infinite loop, Ctrl+C multiple times to exit.")
46+
while True:
47+
sleep(5)
48+
if a > 5:
49+
a = 0
50+
main_fan.set_speed(a)
51+
a += 1

src/ham/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.5.4"
1+
__version__ = "0.5.5"
22

33
from .manager import MqttManager
44

src/ham/fan.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
from abc import abstractmethod
2+
import logging
3+
4+
from .things import Thing
5+
from .utils import WrapperCallback
6+
7+
logger = logging.getLogger(__name__)
8+
9+
10+
class Fan(Thing):
11+
"""Basic class for a Fan entity.
12+
13+
Fan entities can have quite a lot of behavior (oscillation, direction,
14+
presets...). For now, fans only consider the speed, set through a single
15+
`command_topic`.
16+
"""
17+
optimistic: bool
18+
speed_range_max: int
19+
speed_range_min: int
20+
21+
config_fields = ["optimistic", "speed_range_max", "speed_range_min"]
22+
23+
_state: bool = False
24+
_speed: int # Used only for fans with speed
25+
26+
@property
27+
def component(self):
28+
return "fan"
29+
30+
@abstractmethod
31+
def callback(self, state: bool):
32+
pass
33+
34+
def raw_callback(self, topic, payload):
35+
print("Here: %s %s" % (topic, payload))
36+
if payload == b'ON':
37+
self._state = True
38+
return self.callback(True)
39+
elif payload == b'OFF':
40+
self._state = False
41+
return self.callback(False)
42+
else:
43+
logger.warning("Ignoring unrecognized command: `%s`", payload)
44+
45+
def get_config(self):
46+
config = super().get_config()
47+
config["command_topic"] = f'~/{ self.short_id }/set'
48+
return config
49+
50+
def set_callbacks(self):
51+
self.mqtt_manager.client.message_callback_add(
52+
f'{ self.mqtt_manager.base_topic }/{ self.short_id }/set',
53+
WrapperCallback(self.raw_callback)
54+
)
55+
56+
57+
class BinaryOptimisticFan(Fan):
58+
"""A fan that tracks its own on/off state."""
59+
@property
60+
def state(self):
61+
return self._state
62+
63+
@state.setter
64+
def state(self, value: bool):
65+
self._state = value
66+
self.publish_state(self._state)
67+
68+
def callback(self, state: bool):
69+
self.state = state
70+
71+
def get_config(self):
72+
config = super().get_config()
73+
config["state_topic"] = f'~/{ self.short_id }/main'
74+
return config
75+
76+
77+
class PercentageOptimisticFan(BinaryOptimisticFan):
78+
"""A Fan that has regulable speed."""
79+
80+
speed_range_min = 1
81+
speed_range_max = 100
82+
_speed = 1
83+
84+
@property
85+
def speed(self):
86+
return self._speed
87+
88+
@speed.setter
89+
def speed(self, value: int):
90+
if value == 0:
91+
self.state = False
92+
# don't touch the _speed, it works as the "last known speed"
93+
else:
94+
self.state = True
95+
96+
self._speed = value
97+
self.publish_mqtt_message(bytes(str(value), "utf-8"), "speed/state")
98+
self.publish_state(self._state)
99+
100+
def speed_callback(self, speed: int):
101+
self.speed = speed
102+
103+
def raw_speed_callback(self, topic: str, raw_speed: bytes):
104+
print("Hey!")
105+
self.speed_callback(int(raw_speed.decode("utf-8")))
106+
107+
def get_config(self):
108+
config = super().get_config()
109+
config["percentage_state_topic"] = f'~/{ self.short_id }/speed/state'
110+
config["percentage_command_topic"] = f'~/{ self.short_id }/speed/set'
111+
return config
112+
113+
def set_callbacks(self):
114+
super().set_callbacks()
115+
self.mqtt_manager.client.message_callback_add(
116+
f'{ self.mqtt_manager.base_topic }/{ self.short_id }/speed/set',
117+
WrapperCallback(self.raw_speed_callback)
118+
)

src/ham/manager.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ def availability_topic(self):
169169
def subscribe_topic(self):
170170
return [
171171
(f"{ self.base_topic }/+/set", 0), # Most things use `set`
172+
(f"{ self.base_topic }/+/+/set", 0), # Some things are nested (at least: fans)
172173
(f"{ self.base_topic }/+/press", 0), # Buttons use `press`
173174
]
174175

0 commit comments

Comments
 (0)