Merge pull request #95 from rubiojr/patch-1
[EVA-2020-02-2.git] / examples / mqtt-all.py
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, SerialTimeoutError
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 DEFAULT_READ_INTERVAL = 5
41
42 # mqtt callbacks
43 def on_connect(client, userdata, flags, 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 return as dict
55 def read_bme280(bme280):
56 # Compensation factor for temperature
57 comp_factor = 2.25
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())
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
76 def read_pms5003(pms5003):
77 values = {}
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)
89 return values
90
91
92 # Get CPU temperature to use for compensation
93 def get_cpu_temperature():
94 process = Popen(
95 ["vcgencmd", "measure_temp"], stdout=PIPE, universal_newlines=True
96 )
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
102 def 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
110 def 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
118 def display_status(disp, mqtt_broker):
119 # Width and height to calculate text position
120 WIDTH = disp.width
121 HEIGHT = disp.height
122 # Text settings
123 font_size = 12
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)
129 device_serial_number = get_serial_number()
130 message = "{}\nWi-Fi: {}\nmqtt-broker: {}".format(
131 device_serial_number, wifi_status, mqtt_broker
132 )
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
143 def main():
144 parser = argparse.ArgumentParser(
145 description="Publish enviroplus values over mqtt"
146 )
147 parser.add_argument(
148 "--broker",
149 default=DEFAULT_MQTT_BROKER_IP,
150 type=str,
151 help="mqtt broker IP",
152 )
153 parser.add_argument(
154 "--port",
155 default=DEFAULT_MQTT_BROKER_PORT,
156 type=int,
157 help="mqtt broker port",
158 )
159 parser.add_argument(
160 "--topic", default=DEFAULT_MQTT_TOPIC, type=str, help="mqtt topic"
161 )
162 parser.add_argument(
163 "--interval",
164 default=DEFAULT_READ_INTERVAL,
165 type=int,
166 help="the read interval in seconds",
167 )
168 args = parser.parse_args()
169
170 # Raspberry Pi ID
171 device_serial_number = get_serial_number()
172 device_id = "raspi-" + device_serial_number
173
174 print(
175 f"""mqtt-all.py - Reads Enviro plus data and sends over mqtt.
176
177 broker: {args.broker}
178 client_id: {device_id}
179 port: {args.port}
180 topic: {args.topic}
181
182 Press Ctrl+C to exit!
183
184 """
185 )
186
187 mqtt_client = mqtt.Client(client_id=device_id)
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
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")
214
215 # Display Raspberry Pi serial and Wi-Fi status
216 print("RPi serial: {}".format(device_serial_number))
217 print("Wi-Fi: {}\n".format("connected" if check_wifi() else "disconnected"))
218 print("MQTT broker IP: {}".format(args.broker))
219
220 # Main loop to read data, display, and send over mqtt
221 mqtt_client.loop_start()
222 while True:
223 try:
224 values = read_bme280(bme280)
225 if HAS_PMS:
226 pms_values = read_pms5003(pms5003)
227 values.update(pms_values)
228 values["serial"] = device_serial_number
229 print(values)
230 mqtt_client.publish(args.topic, json.dumps(values))
231 display_status(disp, args.broker)
232 time.sleep(args.interval)
233 except Exception as e:
234 print(e)
235
236
237 if __name__ == "__main__":
238 main()