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