Catch #61 with tests and fix
[EVA-2020-02-2.git] / examples / luftdaten.py
1 #!/usr/bin/env python3
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 from fonts.ttf import RobotoMedium as UserFont
11
12 try:
13 from smbus2 import SMBus
14 except ImportError:
15 from smbus import SMBus
16
17 print("""luftdaten.py - Reads temperature, pressure, humidity,
18 PM2.5, and PM10 from Enviro plus and sends data to Luftdaten,
19 the citizen science air quality project.
20
21 Note: you'll need to register with Luftdaten at:
22 https://meine.luftdaten.info/ and enter your Raspberry Pi
23 serial number that's displayed on the Enviro plus LCD along
24 with the other details before the data appears on the
25 Luftdaten map.
26
27 Press Ctrl+C to exit!
28
29 """)
30
31 bus = SMBus(1)
32
33 # Create BME280 instance
34 bme280 = BME280(i2c_dev=bus)
35
36 # Create LCD instance
37 disp = 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
47 disp.begin()
48
49 # Create PMS5003 instance
50 pms5003 = PMS5003()
51
52
53 # Read values from BME280 and PMS5003 and return as dict
54 def 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())
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:
67 pms5003.reset()
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 return values
72
73
74 # Get CPU temperature to use for compensation
75 def get_cpu_temperature():
76 process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE, universal_newlines=True)
77 output, _error = process.communicate()
78 return float(output[output.index('=') + 1:output.rindex("'")])
79
80
81 # Get Raspberry Pi serial number to use as ID
82 def get_serial_number():
83 with open('/proc/cpuinfo', 'r') as f:
84 for line in f:
85 if line[0:6] == 'Serial':
86 return line.split(":")[1].strip()
87
88
89 # Check for Wi-Fi connection
90 def check_wifi():
91 if check_output(['hostname', '-I']):
92 return True
93 else:
94 return False
95
96
97 # Display Raspberry Pi serial and Wi-Fi status on LCD
98 def 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
113
114 def 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 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 }
133 )
134
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 }
147 )
148
149 if resp_1.ok and resp_2.ok:
150 return True
151 else:
152 return False
153
154
155 # Compensation factor for temperature
156 comp_factor = 2.25
157
158 # Raspberry Pi ID to send to Luftdaten
159 id = "raspi-" + get_serial_number()
160
161 # Width and height to calculate text position
162 WIDTH = disp.width
163 HEIGHT = disp.height
164
165 # Text settings
166 font_size = 16
167 font = ImageFont.truetype(UserFont, font_size)
168
169 # Display Raspberry Pi serial and Wi-Fi status
170 print("Raspberry Pi serial: {}".format(get_serial_number()))
171 print("Wi-Fi: {}\n".format("connected" if check_wifi() else "disconnected"))
172
173 time_since_update = 0
174 update_time = time.time()
175
176 # Main loop to read data, display, and send to Luftdaten
177 while True:
178 try:
179 time_since_update = time.time() - update_time
180 values = read_values()
181 print(values)
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"))
186 display_status()
187 except Exception as e:
188 print(e)