Added user projects section to README
[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
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()