-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.py
233 lines (181 loc) · 8.97 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
#!python3
from HHCIODriver import HHCIODriver
import logging
import os
import paho.mqtt.client as mqtt
import sys
import threading
import time
import hashlib
mqtt_prefix = os.getenv("MQTT_PREFIX")
mqtt_base = "devices/" + str(mqtt_prefix) + "/"
mqtt_client_id = "HHC-MQTT-Bridge-" + str(hashlib.sha256(os.urandom(32)).hexdigest()[:7])
mqtt_client = mqtt.Client(mqtt_client_id)
device_driver = None
def on_mqtt_message(client, userdata, message):
global mqtt_base, device_driver
payload = str(message.payload.decode("utf-8"))
topic = str(message.topic)
try:
if (mqtt_base + "outputs") in topic:
topic_parts = topic.split("/")
if len(topic_parts) == 5:
output_number = int(topic_parts[3])
logging.info("Received MQTT message to command output {} to state {}".format(output_number, payload))
device_driver.operate_relay(output_number, (payload == "ON"))
# Here, we inform the MQTT client or Home Assistant about the new potential state of the relay.
# It is possible that the relay's state did not change, so output polling might revert the state
# back to its previous or unchanged condition.
if payload == "ON":
client.publish(mqtt_base + "outputs/{}/state".format(output_number), "ON")
else:
client.publish(mqtt_base + "outputs/{}/state".format(output_number), "OFF")
# Check whether Home Assistant was rebooted needs to
# republish discovery info to re-register the device
elif "homeassistant/status" in topic:
if os.getenv("HA_COMPATIBLE") is not None:
if payload == "online":
publish_ha_discovery_info(client)
except ConnectionError as e:
#device is not available at this time
pass
except Exception as e:
logging.warning("Ignoring an exception ({}) that occurred while processing an incoming MQTT message.".format(e))
def on_mqtt_connect(client, userdata, flags, rc):
global mqtt_base
client.subscribe(mqtt_base + "outputs/1/command")
client.subscribe(mqtt_base + "outputs/2/command")
client.subscribe(mqtt_base + "outputs/3/command")
client.subscribe(mqtt_base + "outputs/4/command")
client.subscribe(mqtt_base + "outputs/5/command")
client.subscribe(mqtt_base + "outputs/6/command")
client.subscribe(mqtt_base + "outputs/7/command")
client.subscribe(mqtt_base + "outputs/8/command")
if os.getenv("HA_COMPATIBLE") is not None:
client.subscribe("homeassistant/status")
logging.info("MQTT is now connected!")
def on_mqtt_disconnect(client, userdata, rc):
global device_driver
logging.warning("MQTT is now disconnected!")
def on_device_connect():
global mqtt_client, mqtt_base
logging.info("Device has come online.")
mqtt_client.publish(mqtt_base + "connection", "online", retain=True)
def on_device_disconnect():
global mqtt_client, mqtt_base, device_driver
logging.warning("Device has gone offline!")
mqtt_client.publish(mqtt_base + "connection", "offline", retain=True)
while not device_driver.connect(skip=True):
logging.warning("Failed to reconnect to the device, backing off for 30 seconds...")
time.sleep(30)
def periodic_input_update(mqtt_client):
global device_driver, mqtt_base
values = {}
while True:
for input in range(1, 9):
try:
value = device_driver.read_input(input)
should_publish = False
if input in values.keys():
if value != values[input]:
should_publish = True
else:
should_publish = True
if should_publish:
values[input] = value
if (value == "0"):
logging.info("Publishing new input value to MQTT: Input {} is now OFF".format(input))
mqtt_client.publish(mqtt_base + "inputs/{}".format(input), "OFF", retain=True)
else:
logging.info("Publishing new input value to MQTT: Input {} is now ON".format(input))
mqtt_client.publish(mqtt_base + "inputs/{}".format(input), "ON", retain=True)
except ConnectionError as e:
# device is not available at this time
time.sleep(3)
except Exception as e:
logging.warning("Ignoring exception ({}) that occurred while reading value of input {}.".format(e, input))
time.sleep(0.5)
# Get the actual relay statuses from the relay controller
def periodic_output_update(mqtt_client):
global device_driver, mqtt_base
while True:
try:
relay_statuses = device_driver.read_outputs()
for x in range(0, 8):
if relay_statuses[x] == "0":
mqtt_client.publish(mqtt_base + "outputs/{}/state".format(x+1), "OFF")
else:
mqtt_client.publish(mqtt_base + "outputs/{}/state".format(x+1), "ON")
except ConnectionError as e:
# device is not available at this time
time.sleep(3)
except Exception as e:
logging.warning("Ignoring exception ({}) that occurred while reading value of relay outputs {}.".format(e, input))
time.sleep(0.5)
def publish_ha_discovery_info(mqtt_client):
global mqtt_prefix, mqtt_base
logging.info("Publishing Home Assistant-compatible discovery messages.")
for input in range(1, 9):
mqtt_client.publish("homeassistant/binary_sensor/{}-input-{}/config".format(mqtt_prefix, input), '{{"name": "{} Input {}", "object_id": "{}_input_{}", "unique_id": "{}_input_{}", "state_topic": "{}inputs/{}", "avty_t": "{}connection", "dev": {{"mf": "Unbranded", "mdl": "HHC-N8I8OP", "ids": "{}", "name": "{}"}}}}'.format(mqtt_prefix, input, mqtt_prefix, input, mqtt_prefix, input, mqtt_base, input, mqtt_base, "HHC-" + mqtt_prefix, mqtt_prefix))
for output in range(1, 9):
mqtt_client.publish("homeassistant/switch/{}-output-{}/config".format(mqtt_prefix, output), '{{"name": "{} Output {}", "object_id": "{}_output_{}", "unique_id": "{}_output_{}", "command_topic": "{}outputs/{}/command", "state_topic": "{}outputs/{}/state", "avty_t": "{}connection", "dev": {{"mf": "Unbranded", "mdl": "HHC-N8I8OP", "ids": "{}", "name": "{}"}}}}'.format(mqtt_prefix, output, mqtt_prefix, output, mqtt_prefix, output, mqtt_base, output, mqtt_base, output, mqtt_base, "HHC-" + mqtt_prefix, mqtt_prefix))
def startup():
global mqtt_client, device_driver, mqtt_prefix, mqtt_base
mqtt_hostname = os.getenv("MQTT_HOSTNAME")
mqtt_port = os.getenv("MQTT_PORT")
mqtt_username = os.getenv("MQTT_USERNAME")
mqtt_password = os.getenv("MQTT_PASSWORD")
device_hostname = os.getenv("DEVICE_HOSTNAME")
device_port = os.getenv("DEVICE_PORT")
log_level = os.getenv("LOG_LEVEL")
if log_level is None:
log_level = logging.INFO
elif log_level == "DEBUG":
log_level = logging.DEBUG
logging.basicConfig(level=log_level)
if mqtt_hostname is None:
logging.critical("MQTT hostname is not defined!")
sys.exit(1)
return
if mqtt_prefix is None:
logging.critical("MQTT prefix is not defined!")
sys.exit(1)
return
if mqtt_port is None or mqtt_port == 0:
logging.info("MQTT port is not defined, assuming default of 1883.")
mqtt_port = 1883
else:
mqtt_port = int(mqtt_port)
if device_hostname is None:
logging.critical("Device hostname is not defined!")
sys.exit(1)
return
if device_port is None or device_port == 0:
logging.info("Device port is not defined, assuming default of 5000.")
device_port = 5000
else:
device_port = int(device_port)
mqtt_client.on_message = on_mqtt_message
mqtt_client.on_connect = on_mqtt_connect
mqtt_client.on_disconnect = on_mqtt_disconnect
if mqtt_username is not None and mqtt_password is not None:
logging.info("Using MQTT password authentication.")
mqtt_client.username_pw_set(mqtt_username, mqtt_password)
try:
mqtt_client.will_set(mqtt_base + "connection", "offline", retain=True)
mqtt_client.connect(mqtt_hostname, mqtt_port)
except:
logging.critical("Could not connect to MQTT server!")
sys.exit(1)
return
mqtt_client.loop_start()
if os.getenv("HA_COMPATIBLE") is not None:
publish_ha_discovery_info(mqtt_client)
device_driver = HHCIODriver(device_hostname, device_port, on_device_connect, on_device_disconnect)
if not device_driver.connect():
logging.critical("Could not connect to the device at startup!")
input_thread = threading.Thread(target=periodic_input_update, args=(mqtt_client,))
output_thread = threading.Thread(target=periodic_output_update, args=(mqtt_client,))
input_thread.start()
output_thread.start()
startup()