Commit | Line | Data |
---|---|---|
f5335ba6 R |
1 | """ |
2 | Run mqtt broker on localhost: sudo apt-get install mosquitto mosquitto-clients | |
3 | ||
4 | Example run: python3 mqtt-all.py --broker 192.168.1.164 --topic enviro | |
5 | """ | |
6 | #!/usr/bin/env python3 | |
7 | ||
8 | import argparse | |
9 | import ST7735 | |
10 | import time | |
11 | from bme280 import BME280 | |
12 | from pms5003 import PMS5003, ReadTimeoutError | |
13 | from enviroplus import gas | |
14 | ||
15 | try: | |
16 | # Transitional fix for breaking change in LTR559 | |
17 | from ltr559 import LTR559 | |
18 | ||
19 | ltr559 = LTR559() | |
20 | except ImportError: | |
21 | import ltr559 | |
22 | ||
23 | from subprocess import PIPE, Popen, check_output | |
24 | from PIL import Image, ImageDraw, ImageFont | |
25 | from fonts.ttf import RobotoMedium as UserFont | |
26 | import json | |
27 | ||
28 | import paho.mqtt.client as mqtt | |
29 | import paho.mqtt.publish as publish | |
30 | ||
31 | try: | |
32 | from smbus2 import SMBus | |
33 | except ImportError: | |
34 | from smbus import SMBus | |
35 | ||
36 | ||
37 | DEFAULT_MQTT_BROKER_IP = "localhost" | |
38 | DEFAULT_MQTT_BROKER_PORT = 1883 | |
39 | DEFAULT_MQTT_TOPIC = "enviroplus" | |
40 | ||
41 | # mqtt callbacks | |
42 | def on_connect(client, userdata, flags, rc): | |
43 | print(f"CONNACK received with code {rc}") | |
44 | if rc == 0: | |
45 | print("connected OK") | |
46 | else: | |
47 | print("Bad connection Returned code=", rc) | |
48 | ||
49 | ||
50 | def on_publish(client, userdata, mid): | |
51 | print("mid: " + str(mid)) | |
52 | ||
53 | ||
54 | # Read values from BME280 and PMS5003 and return as dict | |
55 | def read_values(bme280, pms5003): | |
56 | # Compensation factor for temperature | |
57 | comp_factor = 2.25 | |
58 | ||
59 | values = {} | |
60 | cpu_temp = get_cpu_temperature() | |
61 | raw_temp = bme280.get_temperature() # float | |
62 | comp_temp = raw_temp - ((cpu_temp - raw_temp) / comp_factor) | |
63 | values["temperature"] = int(comp_temp) | |
64 | values["pressure"] = round( | |
65 | int(bme280.get_pressure() * 100), -1 | |
66 | ) # round to nearest 10 | |
67 | values["humidity"] = int(bme280.get_humidity()) | |
68 | try: | |
69 | pm_values = pms5003.read() # int | |
70 | values["pm1"] = pm_values.pm_ug_per_m3(1) | |
71 | values["pm25"] = pm_values.pm_ug_per_m3(2.5) | |
72 | values["pm10"] = pm_values.pm_ug_per_m3(10) | |
73 | except ReadTimeoutError: | |
74 | pms5003.reset() | |
75 | pm_values = pms5003.read() | |
76 | values["pm1"] = pm_values.pm_ug_per_m3(1) | |
77 | values["pm25"] = pm_values.pm_ug_per_m3(2.5) | |
78 | values["pm10"] = pm_values.pm_ug_per_m3(10) | |
79 | data = gas.read_all() | |
80 | values["oxidised"] = int(data.oxidising / 1000) | |
81 | values["reduced"] = int(data.reducing / 1000) | |
82 | values["nh3"] = int(data.nh3 / 1000) | |
83 | values["lux"] = int(ltr559.get_lux()) | |
84 | return values | |
85 | ||
86 | ||
87 | # Get CPU temperature to use for compensation | |
88 | def get_cpu_temperature(): | |
89 | process = Popen(["vcgencmd", "measure_temp"], stdout=PIPE, universal_newlines=True) | |
90 | output, _error = process.communicate() | |
91 | return float(output[output.index("=") + 1 : output.rindex("'")]) | |
92 | ||
93 | ||
94 | # Get Raspberry Pi serial number to use as ID | |
95 | def get_serial_number(): | |
96 | with open("/proc/cpuinfo", "r") as f: | |
97 | for line in f: | |
98 | if line[0:6] == "Serial": | |
99 | return line.split(":")[1].strip() | |
100 | ||
101 | ||
102 | # Check for Wi-Fi connection | |
103 | def check_wifi(): | |
104 | if check_output(["hostname", "-I"]): | |
105 | return True | |
106 | else: | |
107 | return False | |
108 | ||
109 | ||
110 | # Display Raspberry Pi serial and Wi-Fi status on LCD | |
111 | def display_status(disp, mqtt_broker): | |
112 | # Width and height to calculate text position | |
113 | WIDTH = disp.width | |
114 | HEIGHT = disp.height | |
115 | # Text settings | |
116 | font_size = 16 | |
117 | font = ImageFont.truetype(UserFont, font_size) | |
118 | ||
119 | wifi_status = "connected" if check_wifi() else "disconnected" | |
120 | text_colour = (255, 255, 255) | |
121 | back_colour = (0, 170, 170) if check_wifi() else (85, 15, 15) | |
122 | id = get_serial_number() | |
123 | message = "{}\nWi-Fi: {}\nmqtt-broker: {}".format(id, wifi_status, mqtt_broker) | |
124 | img = Image.new("RGB", (WIDTH, HEIGHT), color=(0, 0, 0)) | |
125 | draw = ImageDraw.Draw(img) | |
126 | size_x, size_y = draw.textsize(message, font) | |
127 | x = (WIDTH - size_x) / 2 | |
128 | y = (HEIGHT / 2) - (size_y / 2) | |
129 | draw.rectangle((0, 0, 160, 80), back_colour) | |
130 | draw.text((x, y), message, font=font, fill=text_colour) | |
131 | disp.display(img) | |
132 | ||
133 | ||
134 | def main(): | |
135 | parser = argparse.ArgumentParser(description="Publish enviroplus values over mqtt") | |
136 | parser.add_argument( | |
137 | "--broker", default=DEFAULT_MQTT_BROKER_IP, type=str, help="mqtt broker IP", | |
138 | ) | |
139 | parser.add_argument( | |
140 | "--port", default=DEFAULT_MQTT_BROKER_PORT, type=int, help="mqtt broker port", | |
141 | ) | |
142 | parser.add_argument( | |
143 | "--topic", default=DEFAULT_MQTT_TOPIC, type=str, help="mqtt topic" | |
144 | ) | |
145 | args = parser.parse_args() | |
146 | ||
147 | print( | |
148 | """mqtt-all.py - Reads temperature, pressure, humidity, | |
149 | PM2.5, and PM10 from Enviro plus and sends data over mqtt. | |
150 | ||
151 | broker: {} | |
152 | port: {} | |
153 | topic: {} | |
154 | ||
155 | Press Ctrl+C to exit! | |
156 | ||
157 | """.format( | |
158 | args.broker, args.port, args.topic | |
159 | ) | |
160 | ) | |
161 | ||
162 | mqtt_client = mqtt.Client() | |
163 | mqtt_client.on_connect = on_connect | |
164 | mqtt_client.on_publish = on_publish | |
165 | mqtt_client.connect(args.broker, port=args.port) | |
166 | ||
167 | bus = SMBus(1) | |
168 | ||
169 | # Create BME280 instance | |
170 | bme280 = BME280(i2c_dev=bus) | |
171 | ||
172 | # Create LCD instance | |
173 | disp = ST7735.ST7735( | |
174 | port=0, cs=1, dc=9, backlight=12, rotation=270, spi_speed_hz=10000000 | |
175 | ) | |
176 | ||
177 | # Initialize display | |
178 | disp.begin() | |
179 | ||
180 | # Create PMS5003 instance | |
181 | pms5003 = PMS5003() | |
182 | ||
183 | # Raspberry Pi ID | |
184 | device_serial_number = get_serial_number() | |
185 | id = "raspi-" + device_serial_number | |
186 | ||
187 | # Display Raspberry Pi serial and Wi-Fi status | |
188 | print("Raspberry Pi serial: {}".format(get_serial_number())) | |
189 | print("Wi-Fi: {}\n".format("connected" if check_wifi() else "disconnected")) | |
190 | print("MQTT broker IP: {}".format(args.broker)) | |
191 | ||
192 | time_since_update = 0 | |
193 | update_time = time.time() | |
194 | ||
195 | # Main loop to read data, display, and send over mqtt | |
196 | mqtt_client.loop_start() | |
197 | while True: | |
198 | try: | |
199 | time_since_update = time.time() - update_time | |
200 | values = read_values(bme280, pms5003) | |
201 | values["serial"] = device_serial_number | |
202 | print(values) | |
203 | mqtt_client.publish(args.topic, json.dumps(values)) | |
204 | if time_since_update > 145: | |
205 | update_time = time.time() | |
206 | display_status(disp, args.broker) | |
207 | except Exception as e: | |
208 | print(e) | |
209 | ||
210 | ||
211 | if __name__ == "__main__": | |
212 | main() |