Merge branch 'master' into enviro-non-plus
[EVA-2020-02-2.git] / examples / luftdaten.py
CommitLineData
20442c9a 1#!/usr/bin/env python3
f7a6afa2 2
f7a6afa2
SM
3import requests
4import ST7735
fcfd8a1d 5import time
f7a6afa2 6from bme280 import BME280
85439fb1 7from pms5003 import PMS5003, ReadTimeoutError
f7a6afa2
SM
8from subprocess import PIPE, Popen, check_output
9from PIL import Image, ImageDraw, ImageFont
20442c9a 10from fonts.ttf import RobotoMedium as UserFont
f7a6afa2
SM
11
12try:
13 from smbus2 import SMBus
14except ImportError:
15 from smbus import SMBus
16
ec075941
SM
17print("""luftdaten.py - Reads temperature, pressure, humidity,
18PM2.5, and PM10 from Enviro plus and sends data to Luftdaten,
19the citizen science air quality project.
f7a6afa2 20
ec075941
SM
21Note: you'll need to register with Luftdaten at:
22https://meine.luftdaten.info/ and enter your Raspberry Pi
23serial number that's displayed on the Enviro plus LCD along
24with the other details before the data appears on the
25Luftdaten map.
f7a6afa2
SM
26
27Press Ctrl+C to exit!
28
29""")
30
31bus = SMBus(1)
32
33# Create BME280 instance
34bme280 = BME280(i2c_dev=bus)
35
f7a6afa2
SM
36# Create LCD instance
37disp = ST7735.ST7735(
38 port=0,
39 cs=1,
40 dc=9,
41 backlight=12,
42 rotation=270,
43 spi_speed_hz=10000000
44)
45
46# Initialize display
47disp.begin()
48
ec075941
SM
49# Create PMS5003 instance
50pms5003 = PMS5003()
51
52
f7a6afa2
SM
53# Read values from BME280 and PMS5003 and return as dict
54def read_values():
55 values = {}
56 cpu_temp = get_cpu_temperature()
57 raw_temp = bme280.get_temperature()
58 comp_temp = raw_temp - ((cpu_temp - raw_temp) / comp_factor)
59 values["temperature"] = "{:.2f}".format(comp_temp)
60 values["pressure"] = "{:.2f}".format(bme280.get_pressure() * 100)
61 values["humidity"] = "{:.2f}".format(bme280.get_humidity())
85439fb1
SM
62 try:
63 pm_values = pms5003.read()
64 values["P2"] = str(pm_values.pm_ug_per_m3(2.5))
65 values["P1"] = str(pm_values.pm_ug_per_m3(10))
66 except ReadTimeoutError:
391bec80 67 pms5003.reset()
85439fb1
SM
68 pm_values = pms5003.read()
69 values["P2"] = str(pm_values.pm_ug_per_m3(2.5))
70 values["P1"] = str(pm_values.pm_ug_per_m3(10))
f7a6afa2
SM
71 return values
72
ec075941 73
f7a6afa2
SM
74# Get CPU temperature to use for compensation
75def get_cpu_temperature():
5f63416d 76 process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE, universal_newlines=True)
f7a6afa2
SM
77 output, _error = process.communicate()
78 return float(output[output.index('=') + 1:output.rindex("'")])
79
ec075941 80
f7a6afa2
SM
81# Get Raspberry Pi serial number to use as ID
82def get_serial_number():
ec075941 83 with open('/proc/cpuinfo', 'r') as f:
f7a6afa2 84 for line in f:
ec075941
SM
85 if line[0:6] == 'Serial':
86 return line.split(":")[1].strip()
87
f7a6afa2
SM
88
89# Check for Wi-Fi connection
90def check_wifi():
91 if check_output(['hostname', '-I']):
92 return True
93 else:
94 return False
95
ec075941 96
f7a6afa2
SM
97# Display Raspberry Pi serial and Wi-Fi status on LCD
98def display_status():
99 wifi_status = "connected" if check_wifi() else "disconnected"
100 text_colour = (255, 255, 255)
101 back_colour = (0, 170, 170) if check_wifi() else (85, 15, 15)
102 id = get_serial_number()
103 message = "{}\nWi-Fi: {}".format(id, wifi_status)
104 img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0))
105 draw = ImageDraw.Draw(img)
106 size_x, size_y = draw.textsize(message, font)
107 x = (WIDTH - size_x) / 2
108 y = (HEIGHT / 2) - (size_y / 2)
109 draw.rectangle((0, 0, 160, 80), back_colour)
110 draw.text((x, y), message, font=font, fill=text_colour)
111 disp.display(img)
112
ec075941 113
f7a6afa2
SM
114def send_to_luftdaten(values, id):
115 pm_values = dict(i for i in values.items() if i[0].startswith("P"))
116 temp_values = dict(i for i in values.items() if not i[0].startswith("P"))
117
118 resp_1 = requests.post("https://api.luftdaten.info/v1/push-sensor-data/",
ec075941
SM
119 json={
120 "software_version": "enviro-plus 0.0.1",
121 "sensordatavalues": [{"value_type": key, "value": val} for
122 key, val in pm_values.items()]
123 },
124 headers={
125 "X-PIN": "1",
126 "X-Sensor": id,
127 "Content-Type": "application/json",
128 "cache-control": "no-cache"
129 }
f7a6afa2
SM
130 )
131
132 resp_2 = requests.post("https://api.luftdaten.info/v1/push-sensor-data/",
ec075941
SM
133 json={
134 "software_version": "enviro-plus 0.0.1",
135 "sensordatavalues": [{"value_type": key, "value": val} for
136 key, val in temp_values.items()]
137 },
138 headers={
139 "X-PIN": "11",
140 "X-Sensor": id,
141 "Content-Type": "application/json",
142 "cache-control": "no-cache"
143 }
f7a6afa2
SM
144 )
145
146 if resp_1.ok and resp_2.ok:
147 return True
148 else:
149 return False
150
ec075941 151
f7a6afa2 152# Compensation factor for temperature
20442c9a 153comp_factor = 2.25
f7a6afa2
SM
154
155# Raspberry Pi ID to send to Luftdaten
156id = "raspi-" + get_serial_number()
157
158# Width and height to calculate text position
159WIDTH = disp.width
160HEIGHT = disp.height
161
162# Text settings
163font_size = 16
20442c9a 164font = ImageFont.truetype(UserFont, font_size)
f7a6afa2
SM
165
166# Display Raspberry Pi serial and Wi-Fi status
167print("Raspberry Pi serial: {}".format(get_serial_number()))
168print("Wi-Fi: {}\n".format("connected" if check_wifi() else "disconnected"))
169
fcfd8a1d
SM
170time_since_update = 0
171update_time = time.time()
172
f7a6afa2
SM
173# Main loop to read data, display, and send to Luftdaten
174while True:
175 try:
fcfd8a1d 176 time_since_update = time.time() - update_time
f7a6afa2
SM
177 values = read_values()
178 print(values)
fcfd8a1d
SM
179 if time_since_update > 145:
180 resp = send_to_luftdaten(values, id)
181 update_time = time.time()
182 print("Response: {}\n".format("ok" if resp else "failed"))
f7a6afa2
SM
183 display_status()
184 except Exception as e:
185 print(e)