|
|
@ -1,7 +1,9 @@
|
|
|
|
import json
|
|
|
|
import json
|
|
|
|
import logging
|
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import os
|
|
|
|
|
|
|
|
import select
|
|
|
|
import signal
|
|
|
|
import signal
|
|
|
|
|
|
|
|
import threading
|
|
|
|
import time
|
|
|
|
import time
|
|
|
|
from contextlib import closing
|
|
|
|
from contextlib import closing
|
|
|
|
from types import FrameType
|
|
|
|
from types import FrameType
|
|
|
@ -20,30 +22,45 @@ PORT = 8883
|
|
|
|
USERNAME = os.environ.get("MQTT_USERNAME", "")
|
|
|
|
USERNAME = os.environ.get("MQTT_USERNAME", "")
|
|
|
|
PASSWORD = os.environ.get("MQTT_PASSWORD", "")
|
|
|
|
PASSWORD = os.environ.get("MQTT_PASSWORD", "")
|
|
|
|
TOPIC = "homeassistant/sensor/thermostat"
|
|
|
|
TOPIC = "homeassistant/sensor/thermostat"
|
|
|
|
|
|
|
|
AVAILABILITY_TOPIC = f"{TOPIC}/availability"
|
|
|
|
|
|
|
|
|
|
|
|
I2CPORT = 1
|
|
|
|
I2CPORT = 1
|
|
|
|
SENSOR_ADDR = 0x77
|
|
|
|
SENSOR_ADDR = 0x77
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEVICE = {
|
|
|
|
|
|
|
|
"manufacturer": "Dustin C. Hatch",
|
|
|
|
|
|
|
|
"name": "RPi Thermostat Display",
|
|
|
|
|
|
|
|
"model": "RPi Thermostat Display",
|
|
|
|
|
|
|
|
"identifiers": [
|
|
|
|
|
|
|
|
os.uname().nodename,
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SENSOR_CONFIG = {
|
|
|
|
SENSOR_CONFIG = {
|
|
|
|
"thermostat_temperature": {
|
|
|
|
"thermostat_temperature": {
|
|
|
|
"device_class": "temperature",
|
|
|
|
"device_class": "temperature",
|
|
|
|
"name": "Thermostat Temperature",
|
|
|
|
"name": "Thermostat Temperature",
|
|
|
|
|
|
|
|
"device": DEVICE,
|
|
|
|
"state_topic": TOPIC,
|
|
|
|
"state_topic": TOPIC,
|
|
|
|
|
|
|
|
"availability_topic": AVAILABILITY_TOPIC,
|
|
|
|
"unit_of_measurement": "°C",
|
|
|
|
"unit_of_measurement": "°C",
|
|
|
|
"value_template": r"{{ value_json.temperature }}",
|
|
|
|
"value_template": r"{{ value_json.temperature }}",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"thermostat_pressure": {
|
|
|
|
"thermostat_pressure": {
|
|
|
|
"device_class": "pressure",
|
|
|
|
"device_class": "pressure",
|
|
|
|
"name": "Thermostat Pressure",
|
|
|
|
"name": "Thermostat Pressure",
|
|
|
|
|
|
|
|
"device": DEVICE,
|
|
|
|
"state_topic": TOPIC,
|
|
|
|
"state_topic": TOPIC,
|
|
|
|
|
|
|
|
"availability_topic": AVAILABILITY_TOPIC,
|
|
|
|
"unit_of_measurement": "hPa",
|
|
|
|
"unit_of_measurement": "hPa",
|
|
|
|
"value_template": r"{{ value_json.pressure }}",
|
|
|
|
"value_template": r"{{ value_json.pressure }}",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"thermostat_humidity": {
|
|
|
|
"thermostat_humidity": {
|
|
|
|
"device_class": "humidity",
|
|
|
|
"device_class": "humidity",
|
|
|
|
"name": "Thermostat Humidity",
|
|
|
|
"name": "Thermostat Humidity",
|
|
|
|
|
|
|
|
"device": DEVICE,
|
|
|
|
"state_topic": TOPIC,
|
|
|
|
"state_topic": TOPIC,
|
|
|
|
|
|
|
|
"availability_topic": AVAILABILITY_TOPIC,
|
|
|
|
"unit_of_measurement": "%",
|
|
|
|
"unit_of_measurement": "%",
|
|
|
|
"value_template": r"{{ value_json.humidity }}",
|
|
|
|
"value_template": r"{{ value_json.humidity }}",
|
|
|
|
},
|
|
|
|
},
|
|
|
@ -52,18 +69,20 @@ SENSOR_CONFIG = {
|
|
|
|
|
|
|
|
|
|
|
|
class Daemon:
|
|
|
|
class Daemon:
|
|
|
|
def __init__(self) -> None:
|
|
|
|
def __init__(self) -> None:
|
|
|
|
self.running = True
|
|
|
|
self.quitpipe = os.pipe()
|
|
|
|
|
|
|
|
self._ready = threading.Event()
|
|
|
|
|
|
|
|
|
|
|
|
def on_signal(self, signum: int, frame: FrameType) -> None:
|
|
|
|
def on_signal(self, signum: int, frame: FrameType) -> None:
|
|
|
|
log.debug("Got signal %d at %s", signum, frame)
|
|
|
|
log.debug("Got signal %d at %s", signum, frame)
|
|
|
|
log.info("Stopping")
|
|
|
|
log.info("Stopping")
|
|
|
|
self.running = False
|
|
|
|
os.close(self.quitpipe[1])
|
|
|
|
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
def run(self):
|
|
|
|
signal.signal(signal.SIGINT, self.on_signal)
|
|
|
|
signal.signal(signal.SIGINT, self.on_signal)
|
|
|
|
signal.signal(signal.SIGTERM, self.on_signal)
|
|
|
|
signal.signal(signal.SIGTERM, self.on_signal)
|
|
|
|
|
|
|
|
|
|
|
|
client = mqtt.Client()
|
|
|
|
client = mqtt.Client()
|
|
|
|
|
|
|
|
client.will_set(AVAILABILITY_TOPIC, "offline", retain=True)
|
|
|
|
client.on_connect = self.on_connect
|
|
|
|
client.on_connect = self.on_connect
|
|
|
|
client.on_message = self.on_message
|
|
|
|
client.on_message = self.on_message
|
|
|
|
client.on_disconnect = self.on_disconnect
|
|
|
|
client.on_disconnect = self.on_disconnect
|
|
|
@ -73,15 +92,20 @@ class Daemon:
|
|
|
|
client.loop_start()
|
|
|
|
client.loop_start()
|
|
|
|
with closing(smbus2.SMBus(I2CPORT)) as bus:
|
|
|
|
with closing(smbus2.SMBus(I2CPORT)) as bus:
|
|
|
|
params = bme280.load_calibration_params(bus, SENSOR_ADDR)
|
|
|
|
params = bme280.load_calibration_params(bus, SENSOR_ADDR)
|
|
|
|
while self.running:
|
|
|
|
self._ready.wait()
|
|
|
|
|
|
|
|
while 1:
|
|
|
|
data = bme280.sample(bus, SENSOR_ADDR, params)
|
|
|
|
data = bme280.sample(bus, SENSOR_ADDR, params)
|
|
|
|
values = {
|
|
|
|
values = {
|
|
|
|
"temperature": int(data.temperature),
|
|
|
|
"temperature": adj(data.temperature),
|
|
|
|
"pressure": int(data.pressure),
|
|
|
|
"pressure": adj(data.pressure),
|
|
|
|
"humidity": int(data.humidity),
|
|
|
|
"humidity": adj(data.humidity),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
client.publish(TOPIC, json.dumps(values))
|
|
|
|
client.publish(TOPIC, json.dumps(values))
|
|
|
|
time.sleep(10)
|
|
|
|
ready = select.select((self.quitpipe[0],), (), (), 10)[0]
|
|
|
|
|
|
|
|
if self.quitpipe[0] in ready:
|
|
|
|
|
|
|
|
os.close(self.quitpipe[0])
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
client.publish(AVAILABILITY_TOPIC, "offline", retain=True)
|
|
|
|
client.disconnect()
|
|
|
|
client.disconnect()
|
|
|
|
client.loop_stop()
|
|
|
|
client.loop_stop()
|
|
|
|
|
|
|
|
|
|
|
@ -94,9 +118,14 @@ class Daemon:
|
|
|
|
):
|
|
|
|
):
|
|
|
|
log.info("Successfully connected to MQTT broker")
|
|
|
|
log.info("Successfully connected to MQTT broker")
|
|
|
|
for key, value in SENSOR_CONFIG.items():
|
|
|
|
for key, value in SENSOR_CONFIG.items():
|
|
|
|
|
|
|
|
value["unique_id"] = f"sensor.{key}"
|
|
|
|
client.publish(
|
|
|
|
client.publish(
|
|
|
|
f"homeassistant/sensor/{key}/config", json.dumps(value)
|
|
|
|
f"homeassistant/sensor/{key}/config",
|
|
|
|
|
|
|
|
json.dumps(value),
|
|
|
|
|
|
|
|
retain=True,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
client.publish(AVAILABILITY_TOPIC, "online", retain=True)
|
|
|
|
|
|
|
|
self._ready.set()
|
|
|
|
|
|
|
|
|
|
|
|
def on_disconnect(self, client, userdata, rc):
|
|
|
|
def on_disconnect(self, client, userdata, rc):
|
|
|
|
log.error("Lost connection to MQTT broker")
|
|
|
|
log.error("Lost connection to MQTT broker")
|
|
|
@ -113,6 +142,10 @@ class Daemon:
|
|
|
|
print("Message", client, userdata, msg)
|
|
|
|
print("Message", client, userdata, msg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def adj(value, p=10):
|
|
|
|
|
|
|
|
return int(value * p) / p
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
def main():
|
|
|
|
logging.basicConfig(level=logging.DEBUG)
|
|
|
|
logging.basicConfig(level=logging.DEBUG)
|
|
|
|
daemon = Daemon()
|
|
|
|
daemon = Daemon()
|
|
|
|