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