Add GitHub actions workflow
[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
be4d0fc9
PH
118 pm_values_json = [{"value_type": key, "value": val} for key, val in pm_values.items()]
119 temp_values_json = [{"value_type": key, "value": val} for key, val in temp_values.items()]
120
121 resp_1 = requests.post(
122 "https://api.luftdaten.info/v1/push-sensor-data/",
123 json={
124 "software_version": "enviro-plus 0.0.1",
125 "sensordatavalues": pm_values_json
126 },
127 headers={
128 "X-PIN": "1",
129 "X-Sensor": id,
130 "Content-Type": "application/json",
131 "cache-control": "no-cache"
132 }
f7a6afa2
SM
133 )
134
be4d0fc9
PH
135 resp_2 = requests.post(
136 "https://api.luftdaten.info/v1/push-sensor-data/",
137 json={
138 "software_version": "enviro-plus 0.0.1",
139 "sensordatavalues": temp_values_json
140 },
141 headers={
142 "X-PIN": "11",
143 "X-Sensor": id,
144 "Content-Type": "application/json",
145 "cache-control": "no-cache"
146 }
f7a6afa2
SM
147 )
148
149 if resp_1.ok and resp_2.ok:
150 return True
151 else:
152 return False
153
ec075941 154
f7a6afa2 155# Compensation factor for temperature
20442c9a 156comp_factor = 2.25
f7a6afa2
SM
157
158# Raspberry Pi ID to send to Luftdaten
159id = "raspi-" + get_serial_number()
160
161# Width and height to calculate text position
162WIDTH = disp.width
163HEIGHT = disp.height
164
165# Text settings
166font_size = 16
20442c9a 167font = ImageFont.truetype(UserFont, font_size)
f7a6afa2
SM
168
169# Display Raspberry Pi serial and Wi-Fi status
170print("Raspberry Pi serial: {}".format(get_serial_number()))
171print("Wi-Fi: {}\n".format("connected" if check_wifi() else "disconnected"))
172
fcfd8a1d
SM
173time_since_update = 0
174update_time = time.time()
175
f7a6afa2
SM
176# Main loop to read data, display, and send to Luftdaten
177while True:
178 try:
fcfd8a1d 179 time_since_update = time.time() - update_time
f7a6afa2
SM
180 values = read_values()
181 print(values)
fcfd8a1d
SM
182 if time_since_update > 145:
183 resp = send_to_luftdaten(values, id)
184 update_time = time.time()
185 print("Response: {}\n".format("ok" if resp else "failed"))
f7a6afa2
SM
186 display_status()
187 except Exception as e:
188 print(e)