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