improved logging with timestamps (instead of print)
[EVA-2020-02-2.git] / examples / luftdaten.py
1 #!/usr/bin/env python
2
3 import requests
4 import ST7735
5 from bme280 import BME280
6 from pms5003 import PMS5003, ReadTimeoutError
7 from subprocess import PIPE, Popen, check_output
8 from PIL import Image, ImageDraw, ImageFont
9
10 try:
11 from smbus2 import SMBus
12 except ImportError:
13 from smbus import SMBus
14
15 import logging
16
17 logging.basicConfig(
18 format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s',
19 level=logging.INFO,
20 datefmt='%Y-%m-%d %H:%M:%S')
21
22 logging.info("""luftdaten.py - Reads temperature, pressure, humidity,
23 PM2.5, and PM10 from Enviro plus and sends data to Luftdaten,
24 the citizen science air quality project.
25
26 Note: you'll need to register with Luftdaten at:
27 https://meine.luftdaten.info/ and enter your Raspberry Pi
28 serial number that's displayed on the Enviro plus LCD along
29 with the other details before the data appears on the
30 Luftdaten map.
31
32 Press Ctrl+C to exit!
33
34 """)
35
36 bus = SMBus(1)
37
38 # Create BME280 instance
39 bme280 = BME280(i2c_dev=bus)
40
41 # Create LCD instance
42 disp = 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
52 disp.begin()
53
54 # Create PMS5003 instance
55 pms5003 = PMS5003()
56
57
58 # Read values from BME280 and PMS5003 and return as dict
59 def 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())
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:
72 pms5003.reset()
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))
76 return values
77
78
79 # Get CPU temperature to use for compensation
80 def get_cpu_temperature():
81 process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE, universal_newlines=True)
82 output, _error = process.communicate()
83 return float(output[output.index('=') + 1:output.rindex("'")])
84
85
86 # Get Raspberry Pi serial number to use as ID
87 def get_serial_number():
88 with open('/proc/cpuinfo', 'r') as f:
89 for line in f:
90 if line[0:6] == 'Serial':
91 return line.split(":")[1].strip()
92
93
94 # Check for Wi-Fi connection
95 def check_wifi():
96 if check_output(['hostname', '-I']):
97 return True
98 else:
99 return False
100
101
102 # Display Raspberry Pi serial and Wi-Fi status on LCD
103 def 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
118
119 def 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/",
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 }
135 )
136
137 resp_2 = requests.post("https://api.luftdaten.info/v1/push-sensor-data/",
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 }
149 )
150
151 if resp_1.ok and resp_2.ok:
152 return True
153 else:
154 return False
155
156
157 # Compensation factor for temperature
158 comp_factor = 1.2
159
160 # Raspberry Pi ID to send to Luftdaten
161 id = "raspi-" + get_serial_number()
162
163 # Width and height to calculate text position
164 WIDTH = disp.width
165 HEIGHT = disp.height
166
167 # Text settings
168 font_size = 16
169 font = ImageFont.truetype("fonts/Asap/Asap-Bold.ttf", font_size)
170
171 # Display Raspberry Pi serial and Wi-Fi status
172 logging.info("Raspberry Pi serial: {}".format(get_serial_number()))
173 logging.info("Wi-Fi: {}\n".format("connected" if check_wifi() else "disconnected"))
174
175 # Main loop to read data, display, and send to Luftdaten
176 while True:
177 try:
178 values = read_values()
179 logging.info(values)
180 resp = send_to_luftdaten(values, id)
181 logging.info("Response: {}\n".format("ok" if resp else "failed"))
182 display_status()
183 except Exception as e:
184 logging.info(e)