├── review-part2 ├── EnviroPlus.png ├── readme.txt ├── micro_using_arecord.py ├── micro_stream_2.py ├── lcdtest.py ├── micro_stream.py ├── lux.py └── micro_SPL.py ├── README.md ├── review-part1 ├── readme.txt ├── Weather_Gas_PM.py └── BME280_new.py └── LICENSE /review-part2/EnviroPlus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHWcave/Enviro-Plus/HEAD/review-part2/EnviroPlus.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Enviro-Plus 2 | Experiments in environmental monitoring with Pimoroni's Enviro+ board and Raspberry Pi 3 | -------------------------------------------------------------------------------- /review-part2/readme.txt: -------------------------------------------------------------------------------- 1 | These are the examples shown in part 2 of my review of the Pimoroni Enviro+ board 2 | 3 | The HACK font is open source and can be downloaded from here 4 | https://sourcefoundry.org/hack/ 5 | -------------------------------------------------------------------------------- /review-part1/readme.txt: -------------------------------------------------------------------------------- 1 | In Part 1 of my review on Youtube (see https://youtu.be/L1kl1kVbmBw) 2 | I discussed changes to the BME280 driver by Pimoroni and I showed a little test program that presents and records the results of the 3 | - BME280 (temperature, pressure, humidity) 4 | - MICS 6814 (three-in-one gas sensor) 5 | - PMS5003 (particulate matter sensor) 6 | 7 | - plus an extra TMP36 temperature sensor to correct the wrong readings from the BME280 8 | 9 | Weather_Gas_PM.py is that program 10 | BME280_new.py is the new driver for the BME280 11 | 12 | To use this software, you must have the Pimoroni Enviro+ library installed (as explained on their website) and keep both the BME280_new.py and Weather_GAS_PM.py in the same folder. 13 | 14 | NOTE: 15 | According to Bosch Sensortec, the internal BME280 temperature_fine variable should be the chip temperature, therefore correction to ambient temperature is wrong and I removed that function from the BME280_new driver and the example. 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 TheHWcave 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /review-part2/micro_using_arecord.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import wave 5 | import numpy as np 6 | 7 | CMD = 'arecord -D dmic_sv -c2 -r 44100 -f S32_LE -t wav -V mono -d 5 /tmp/recording/rec.wav' 8 | 9 | Done = False 10 | while not Done: 11 | try: 12 | os.system(CMD) 13 | w = wave.open('/tmp/recording/rec.wav','rb') 14 | p = w.getparams() 15 | framerate = w.getframerate() 16 | samplewidth= w.getsampwidth() 17 | nframes = w.getnframes() 18 | # 19 | # read all the wav file data into sdata (array of bytes) 20 | sdata = w.readframes(nframes) 21 | # convert to 32-bit signed integer array 22 | idata2ch = np.frombuffer(bytes(sdata),'=2000: 28 | fdata = np.frombuffer(bytes(sdata),'= n/20: c= '#' 43 | print(c,end='') 44 | print('\r',end='') 45 | MSec = MSec + 250 46 | 47 | except KeyboardInterrupt: 48 | print() 49 | stream.stop_stream() 50 | stream.close() 51 | audio.terminate() 52 | quit() 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /review-part2/lcdtest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import ST7735 4 | from PIL import Image, ImageDraw, ImageFont 5 | from time import sleep 6 | 7 | # Create LCD class instance. 8 | disp = ST7735.ST7735( 9 | port=0, 10 | cs=1, 11 | dc=9, 12 | backlight=12, 13 | rotation=270, 14 | spi_speed_hz=10000000 15 | ) 16 | font_size = 20 17 | font = ImageFont.truetype("ttf/Hack-Regular.ttf", font_size) 18 | text_colour = (255, 255, 255) 19 | back_colour = (0, 170, 0) 20 | 21 | Done = False 22 | 23 | 24 | # Width and height to calculate text position. 25 | WIDTH = disp.width 26 | HEIGHT = disp.height 27 | 28 | 29 | # New canvas to draw on. 30 | img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0)) 31 | draw = ImageDraw.Draw(img) 32 | 33 | Pic = Image.open("EnviroPlus.png") 34 | Scale = 1.0 35 | Wscale = Pic.width / WIDTH 36 | Hscale = Pic.height / HEIGHT 37 | if Wscale > Hscale: 38 | Scale = Wscale 39 | else: 40 | Scale = Hscale 41 | Pwidth = int(Pic.width/Scale) 42 | Pheight= int(Pic.height/Scale) 43 | SPic = Pic.resize((Pwidth,Pheight)) 44 | 45 | Px = int((WIDTH -Pwidth)/2) 46 | Py = int((HEIGHT -Pheight)/2) 47 | while not Done: 48 | try: 49 | draw.rectangle((0, 0, 160, 80), back_colour) 50 | img.paste(SPic,(Px,Py)) 51 | disp.display(img) 52 | sleep(5.0) 53 | 54 | 55 | 56 | message = " Hi there! Reviewing the Enviro+ ..." 57 | size_x, size_y = draw.textsize(message, font) 58 | # Calculate Y text position to center it 59 | y = (HEIGHT / 2) - (size_y / 2) 60 | for c in message: 61 | # Draw background rectangle and write text. 62 | draw.rectangle((0, 0, 160, 80), back_colour) 63 | draw.text((0, y), message, font=font, fill=text_colour) 64 | disp.display(img) 65 | message = message[1::]+message[0] 66 | sleep(0.1) 67 | except KeyboardInterrupt: 68 | disp.set_backlight(0) 69 | quit() 70 | -------------------------------------------------------------------------------- /review-part2/micro_stream.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import pyaudio 4 | import numpy as np 5 | 6 | RATE = 44100 7 | CHUNK = 44100 8 | CHANNEL=1 9 | audio = pyaudio.PyAudio() 10 | 11 | print('++++++++ default host api info +++++++') 12 | info = audio.get_default_host_api_info() 13 | print(info) 14 | print('++++++++ default input device info +++++++') 15 | print(audio.get_default_input_device_info()) 16 | print('++++++++ device count +++++++') 17 | print(audio.get_device_count()) 18 | print('++++++++ host api count +++++++') 19 | print(audio.get_host_api_count()) 20 | 21 | for n in range(audio.get_host_api_count()): 22 | print('==== host api count:'+str(n)) 23 | print(audio.get_host_api_info_by_index(n)) 24 | for n in range(audio.get_device_count()): 25 | print('==== host api 0 device count:'+str(n)) 26 | print(audio.get_device_info_by_host_api_device_index(0,n)) 27 | 28 | 29 | stream = audio.open(format=pyaudio.paInt32, 30 | input_device_index = 4, 31 | channels = CHANNEL, 32 | rate = RATE, 33 | input = True, 34 | frames_per_buffer=CHUNK) 35 | 36 | Seconds = 0 37 | Done = False 38 | while not Done: 39 | try: 40 | sdata = stream.read(CHUNK) 41 | if Seconds >=2: 42 | fdata = np.frombuffer(bytes(sdata),'= n/10: c= '#' 55 | print(c,end='') 56 | print('\r',end='') 57 | Seconds = Seconds + 1 58 | 59 | 60 | except KeyboardInterrupt: 61 | print() 62 | stream.stop_stream() 63 | stream.close() 64 | audio.terminate() 65 | quit() 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /review-part2/lux.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from ltr559 import LTR559 3 | import ST7735 4 | from PIL import Image, ImageDraw, ImageFont 5 | from time import sleep 6 | 7 | 8 | ltr559 = LTR559() 9 | 10 | # Create LCD class instance. 11 | disp = ST7735.ST7735( 12 | port=0, 13 | cs=1, 14 | dc=9, 15 | backlight=12, 16 | rotation=270, 17 | spi_speed_hz=10000000 18 | ) 19 | font_size = 12 20 | font = ImageFont.truetype("ttf/Hack-Regular.ttf", font_size) 21 | text_colour = (255, 255, 255) 22 | back_colour = (0, 0, 0) 23 | 24 | WIDTH = disp.width 25 | HEIGHT = disp.height 26 | # New canvas to draw on. 27 | img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0)) 28 | draw = ImageDraw.Draw(img) 29 | 30 | 31 | def Display_Range(Prompt,Min,Now,Max,Xpos,Colour): 32 | txtsize_x, txtsize_y = draw.textsize(Prompt, font) 33 | Ypos = 0 34 | draw.text((Xpos, Ypos), Prompt, font=font, fill=Colour) 35 | Ypos = Ypos + txtsize_y+5 36 | draw.text((Xpos, Ypos), '{:8.1f}'.format(Max), font=font, fill=(255,0,0)) 37 | Ypos = Ypos + txtsize_y+5 38 | draw.text((Xpos, Ypos), '{:8.1f}'.format(Now), font=font, fill=(0,255,0)) 39 | Ypos = Ypos + txtsize_y+5 40 | draw.text((Xpos, Ypos), '{:8.1f}'.format(Min), font=font, fill=(255,255,0)) 41 | 42 | 43 | 44 | Record = False 45 | MSec = 0 46 | MaxLux = 0.0 47 | MinLux = 1e12 48 | MaxProx = 0.0 49 | MinProx = 1e12 50 | 51 | LUXRANGE = [(96, 0.01, 600.0), 52 | (48, 0.02,1300.0), 53 | (8, 0.125,8000.0), 54 | (4, 0.25,16000.0), 55 | (2, 0.5,32000.0), 56 | (1, 1.0,64000.0)] 57 | 58 | 59 | def AutoLux(): 60 | gain = ltr559.get_gain() 61 | Lux = ltr559.get_lux() 62 | bestgain = 1 63 | for r in LUXRANGE: 64 | if Lux < r[2]: 65 | bestgain = r[0] 66 | break 67 | if bestgain != gain: 68 | ltr559.set_light_options(True,bestgain) 69 | sleep(0.5) 70 | Lux = ltr559.get_lux() 71 | print('gain:'+str(ltr559.get_gain())+' lux='+str(Lux)) 72 | return Lux 73 | 74 | Done = False 75 | 76 | ltr559.set_light_integration_time_ms(100) 77 | 78 | if Record: 79 | fo = open('Lux-Test.csv','w') 80 | fo.write('t,MinLux,Lux,MaxLux,MinProx,Prox,MaxProx\n') 81 | 82 | while not Done: 83 | try: 84 | Lux = AutoLux() 85 | 86 | if Lux > MaxLux: MaxLux = Lux 87 | if Lux < MinLux: MinLux = Lux 88 | Prox = ltr559.get_proximity() 89 | if Prox > MaxProx: MaxProx = Prox 90 | if Prox < MinProx: MinProx = Prox 91 | 92 | 93 | 94 | draw.rectangle((0, 0, 160, 80), back_colour) 95 | Display_Range(' Lux ',MinLux,Lux,MaxLux,0,(255,255,255)) 96 | Display_Range(' Prox ',MinProx,Prox,MaxProx,80,(255,255,255)) 97 | disp.display(img) 98 | 99 | if Record: 100 | fo.write('{:5d}'.format(MSec)) 101 | fo.write(',{:+10.0f}'.format(MinLux)) 102 | fo.write(',{:+10.0f}'.format(Lux)) 103 | fo.write(',{:+10.0f}'.format(MaxLux)) 104 | fo.write(',{:+10.0f}'.format(MinProx)) 105 | fo.write(',{:+10.0f}'.format(Prox)) 106 | fo.write(',{:+10.0f}'.format(MaxProx)) 107 | sleep(1.0) 108 | MSec = MSec + 1000 109 | 110 | if Prox > 200: 111 | MaxLux = 0.0 112 | MaxProx = 0.0 113 | 114 | except KeyboardInterrupt: 115 | print() 116 | if Record: fo.close() 117 | disp.set_backlight(0) 118 | quit() 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /review-part2/micro_SPL.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import pyaudio 3 | import numpy as np 4 | import math 5 | import ST7735 6 | from PIL import Image, ImageDraw, ImageFont 7 | from time import sleep 8 | 9 | # Create LCD class instance. 10 | disp = ST7735.ST7735( 11 | port=0, 12 | cs=1, 13 | dc=9, 14 | backlight=12, 15 | rotation=270, 16 | spi_speed_hz=10000000 17 | ) 18 | font_size = 12 19 | font = ImageFont.truetype("ttf/Hack-Regular.ttf", font_size) 20 | text_colour = (255, 255, 255) 21 | back_colour = (0, 0, 0) 22 | 23 | WIDTH = disp.width 24 | HEIGHT = disp.height 25 | # New canvas to draw on. 26 | img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0)) 27 | draw = ImageDraw.Draw(img) 28 | 29 | RATE = 22000 30 | CHUNK = 22000 31 | CHANNEL=1 32 | audio = pyaudio.PyAudio() 33 | 34 | FSdbSPL = 120 35 | 36 | def Display_SPL(Prompt,SPL,Ypos,Barcolour): 37 | txt = Prompt + '{:3.0f} :'.format(SPL) 38 | txtsize_x, txtsize_y = draw.textsize(txt, font) 39 | draw.text((0, Ypos), txt, font=font, fill=text_colour) 40 | bar_len = int(SPL * ((WIDTH - txtsize_x)/FSdbSPL)) 41 | draw.rectangle((txtsize_x,Ypos,txtsize_x+bar_len,Ypos+txtsize_y),fill=Barcolour) 42 | 43 | 44 | def PCM_to_dbSPL(pcm): 45 | res = 0.0 46 | if pcm > 0: 47 | dbFS = 20*math.log10(pcm/0x1ffff) 48 | res = FSdbSPL +dbFS 49 | return res 50 | 51 | print('++++++++ default host api info +++++++') 52 | info = audio.get_default_host_api_info() 53 | 54 | stream = audio.open(format=pyaudio.paInt32, 55 | input_device_index = info['defaultInputDevice'], 56 | channels = CHANNEL, 57 | rate = RATE, 58 | input = True, 59 | frames_per_buffer=CHUNK) 60 | 61 | Record = False 62 | MSec = 0 63 | Sum = 0.0 64 | Cnt = 0 65 | noisefloor = 1e12 66 | maxSPL = 0.0 67 | Done = False 68 | if Record: 69 | fo = open('SPL-Test.csv','w') 70 | fo.write('t,vmin,vmax,DCOFFSET,vmin_nodc,vmax_nodc,vabs,noisefloor,vfinal,dbSPL,maxSPL\n') 71 | 72 | while not Done: 73 | try: 74 | sdata = stream.read(CHUNK) 75 | if MSec >=3000: 76 | idata = np.frombuffer(bytes(sdata),' abs(vmin_nodc): 84 | vabs = abs(vmax_nodc) 85 | else: 86 | vabs = abs(vmin_nodc) 87 | if vabs < noisefloor: noisefloor = vabs 88 | 89 | vfinal = vabs - noisefloor 90 | 91 | dbSPL = PCM_to_dbSPL(vfinal) 92 | if dbSPL > maxSPL: 93 | maxSPL = dbSPL 94 | 95 | print(MSec) 96 | draw.rectangle((0, 0, 160, 80), back_colour) 97 | Display_SPL('now',dbSPL,0,(0,170,0)) 98 | Display_SPL('max',maxSPL,20,(255,0,0)) 99 | draw.text((0, 40),'nfloor {:3.1f}'.format(noisefloor), font=font, fill=(255,255,255)) 100 | draw.text((0, 60),'dcoffs {:+6.0f}'.format(DCoffset), font=font, fill=(255,255,255)) 101 | disp.display(img) 102 | 103 | if Record: 104 | fo.write('{:5d}'.format(MSec)) 105 | fo.write(',{:+10.0f}'.format(vmin)) 106 | fo.write(',{:+10.0f}'.format(vmax)) 107 | fo.write(',{:+10.0f}'.format(DCoffset)) 108 | fo.write(',{:+10.0f}'.format(vmin_nodc)) 109 | fo.write(',{:+10.0f}'.format(vmax_nodc)) 110 | fo.write(',{:+10.0f}'.format(vabs)) 111 | fo.write(',{:+10.0f}'.format(noisefloor)) 112 | fo.write(',{:+10.0f}'.format(vfinal)) 113 | fo.write(',{:4.0f}'.format(dbSPL)) 114 | fo.write(',{:4.0f}'.format(maxSPL)) 115 | fo.write('\n') 116 | MSec = MSec + 1000 117 | 118 | except KeyboardInterrupt: 119 | print() 120 | if Record: fo.close() 121 | stream.stop_stream() 122 | stream.close() 123 | audio.terminate() 124 | disp.set_backlight(0) 125 | quit() 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /review-part1/Weather_Gas_PM.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #MIT License 3 | # 4 | #Copyright (c) 2019 TheHWcave 5 | # 6 | #Permission is hereby granted, free of charge, to any person obtaining a copy 7 | #of this software and associated documentation files (the "Software"), to deal 8 | #in the Software without restriction, including without limitation the rights 9 | #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | #copies of the Software, and to permit persons to whom the Software is 11 | #furnished to do so, subject to the following conditions: 12 | # 13 | #The above copyright notice and this permission notice shall be included in all 14 | #copies or substantial portions of the Software. 15 | # 16 | #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | #SOFTWARE. 23 | # 24 | import RPi.GPIO as GPIO 25 | import ads1015 26 | from time import sleep, time, localtime,strftime,perf_counter 27 | from BME280_new import BME280_new 28 | from pms5003 import PMS5003, ReadTimeoutError 29 | 30 | try: 31 | from smbus2 import SMBus 32 | except ImportError: 33 | from smbus import SMBus 34 | 35 | 36 | MICS6814_HEATER_PIN = 24 37 | ads1015.I2C_ADDRESS_DEFAULT = ads1015.I2C_ADDRESS_ALTERNATE 38 | 39 | adc = ads1015.ADS1015(i2c_addr=0x49) 40 | adc.set_mode('single') 41 | adc.set_programmable_gain(4.096) 42 | adc.set_sample_rate(128) 43 | 44 | bus = SMBus(1) 45 | bme280 = BME280_new(i2c_dev=bus) 46 | bme280.setup(mode='forced', 47 | temperature_oversampling=1, 48 | pressure_oversampling=1, 49 | humidity_oversampling=1) 50 | 51 | 52 | GPIO.setwarnings(False) 53 | GPIO.setmode(GPIO.BCM) 54 | GPIO.setup(MICS6814_HEATER_PIN, GPIO.OUT) 55 | GPIO.output(MICS6814_HEATER_PIN, 1) 56 | 57 | pms5003 = PMS5003() 58 | sleep(1.0) 59 | 60 | def read_ADS(ch): 61 | if ch < 3: 62 | channel_name = 'in'+chr(48+ch)+'/gnd' 63 | else: 64 | channel_name = 'ref/gnd' 65 | adc.set_programmable_gain(4.096) 66 | Ri = 6000000 67 | v = adc.get_voltage(channel_name) 68 | if v <= 1.0: 69 | adc.set_programmable_gain(1.024) 70 | v = adc.get_voltage(channel_name) 71 | Ri = 3000000 72 | elif v <= 2.0: 73 | adc.set_programmable_gain(2.048) 74 | v = adc.get_voltage(channel_name) 75 | Ri = 6000000 76 | return v, Ri 77 | 78 | def read_MOS(ch): 79 | v, Ri = read_ADS(ch) 80 | return 1.0/ ((1.0/((v * 56000.0) / (3.3 - v))) - (1.0 / Ri)) 81 | 82 | def read_Temp(ch): 83 | v, Ri = read_ADS(ch) 84 | return 100.0 * (v - 0.5) 85 | 86 | 87 | 88 | (temperature,pressure,humidity)= bme280.update_sensor() 89 | tmp2 = read_Temp(3) 90 | 91 | 92 | out_name = 'LOG_'+strftime('%Y%m%d%H%M%S',localtime())+'.csv' 93 | f = open(out_name,'w') 94 | f.write('Seconds,Tmp2, Tmp, Pres, Hum, PM1.0, PM2.5, PM10, S0.3, S0.5, S1.0, S2.5, S5, S10, Ox,Red,NH3\n') 95 | 96 | 97 | 98 | try: 99 | start = perf_counter() 100 | while True: 101 | ox = read_MOS(0) 102 | red = read_MOS(1) 103 | NH3 = read_MOS(2) 104 | tmp2 = read_Temp(3) 105 | (temperature,pressure,humidity)= bme280.update_sensor() 106 | 107 | 108 | Done = False 109 | while not Done: 110 | try: 111 | readings = pms5003.read() 112 | Done = True 113 | except ReadTimeoutError: 114 | pms5003 = PMS5003() 115 | 116 | pm2p5 = readings.pm_ug_per_m3(2.5,atmospheric_environment=True) 117 | pm1 = readings.pm_ug_per_m3(1.0,atmospheric_environment=True) 118 | pm10 = readings.pm_ug_per_m3(None,atmospheric_environment=True) 119 | 120 | s0p3 = readings.pm_per_1l_air(0.3) 121 | s0p5 = readings.pm_per_1l_air(0.5) 122 | s1p0 = readings.pm_per_1l_air(1.0) 123 | s2p5 = readings.pm_per_1l_air(2.5) 124 | s5 = readings.pm_per_1l_air(5) 125 | s10 = readings.pm_per_1l_air(10) 126 | 127 | now = perf_counter() - start 128 | f.write('{:06.1f},'.format(now)) 129 | f.write('{:05.2f},'.format(tmp2)) 130 | f.write('{:05.2f},'.format(temperature)) 131 | f.write('{:05.2f},'.format(pressure)) 132 | f.write('{:05.2f},'.format(humidity)) 133 | f.write('{:03d},'.format(pm1)) 134 | f.write('{:03d},'.format(pm2p5)) 135 | f.write('{:03d},'.format(pm10)) 136 | f.write('{:5d},'.format(s0p3)) 137 | f.write('{:5d},'.format(s0p5)) 138 | f.write('{:5d},'.format(s1p0)) 139 | f.write('{:5d},'.format(s2p5)) 140 | f.write('{:5d},'.format(s5)) 141 | f.write('{:5d},'.format(s10)) 142 | f.write('{:10.2f},'.format(ox)) 143 | f.write('{:10.2f},'.format(red)) 144 | f.write('{:10.2f}\n'.format(NH3)) 145 | 146 | print('time stamp {: >10.1f} Sec'.format(now)) 147 | print('ext temperature {: >10.2f} degC'.format(tmp2)) 148 | print('BME280 temperature {: >10.2f} degC'.format(temperature)) 149 | print('BME280 pressure {: >10.1f} mbar'.format(pressure)) 150 | print('BME280 humitity {: >10.2f}%'.format(humidity)) 151 | print('Oxidizing {: >10.0f} Ohm'.format(ox)) 152 | print('Reducing {: >10.0f} Ohm'.format(red)) 153 | print('Ammonia {: >10.0f} Ohm'.format(NH3)) 154 | print('PM1.0 {: >10d} ug/m3'.format(pm1)) 155 | print('PM2.5 {: >10d} ug/m3'.format(pm2p5)) 156 | print('PM10 {: >10d} ug/m3'.format(pm10)) 157 | print('particles >0.3um {: >10d}'.format(s0p3)) 158 | print('particles >0.5um {: >10d}'.format(s0p5)) 159 | print('particles >1.0um {: >10d}'.format(s1p0)) 160 | print('particles >2.5um {: >10d}'.format(s2p5)) 161 | print('particles >5um {: >10d}'.format(s5)) 162 | print('particles >10um {: >10d}'.format(s10)) 163 | print('\n\n\n') 164 | 165 | 166 | 167 | sleep(1.0) 168 | except KeyboardInterrupt: 169 | f.close() 170 | GPIO.output(MICS6814_HEATER_PIN, 0) 171 | pass 172 | -------------------------------------------------------------------------------- /review-part1/BME280_new.py: -------------------------------------------------------------------------------- 1 | """BME280 Driver.""" 2 | # original from Pimoroni. Modified TheHWcave 3 | from i2cdevice import Device, Register, BitField, _int_to_bytes 4 | from i2cdevice.adapter import LookupAdapter, Adapter 5 | import struct 6 | import time 7 | 8 | 9 | __version__ = '9.9.9.9' #TheHWcave: Bump from 0.0.1 10 | 11 | 12 | CHIP_ID = 0x60 13 | I2C_ADDRESS_GND = 0x76 14 | I2C_ADDRESS_VCC = 0x77 15 | 16 | 17 | class S8Adapter(Adapter): 18 | """Convert unsigned 8bit integer to signed.""" 19 | 20 | def _decode(self, value): 21 | if value & (1 << 7): 22 | value -= 1 << 8 23 | return value 24 | 25 | 26 | class S16Adapter(Adapter): 27 | """Convert unsigned 16bit integer to signed.""" 28 | 29 | def _decode(self, value): 30 | return struct.unpack('> 4) & 0x0F) | (b[1] << 4) 44 | if r & (1 << 11): 45 | r = r - 1 << 12 46 | return r 47 | 48 | 49 | class H4Adapter(S16Adapter): 50 | def _decode(self, value): 51 | b = _int_to_bytes(value, 2) 52 | r = (b[0] << 4) | (b[1] & 0x0F) 53 | if r & (1 << 11): 54 | r = r - 1 << 12 55 | return r 56 | 57 | 58 | class BME280Calibration(): 59 | def __init__(self): 60 | self.dig_t1 = 0 61 | self.dig_t2 = 0 62 | self.dig_t3 = 0 63 | 64 | self.dig_p1 = 0 65 | self.dig_p2 = 0 66 | self.dig_p3 = 0 67 | self.dig_p4 = 0 68 | self.dig_p5 = 0 69 | self.dig_p6 = 0 70 | self.dig_p7 = 0 71 | self.dig_p8 = 0 72 | self.dig_p9 = 0 73 | 74 | self.dig_h1 = 0 75 | self.dig_h2 = 0 76 | self.dig_h3 = 0 77 | self.dig_h4 = 0 78 | self.dig_h5 = 0 79 | self.dig_h6 = 0 80 | 81 | self.temperature_fine = 0 82 | 83 | 84 | 85 | 86 | def compensate_temperature(self, raw_temperature): 87 | var1 = (raw_temperature / 16384.0 - self.dig_t1 / 1024.0) * self.dig_t2 88 | var2 = raw_temperature / 131072.0 - self.dig_t1 / 8192.0 89 | var2 = var2 * var2 * self.dig_t3 90 | self.temperature_fine = (var1 + var2) 91 | return self.temperature_fine / 5120.0 92 | 93 | def compensate_pressure(self, raw_pressure): 94 | var1 = self.temperature_fine / 2.0 - 64000.0 95 | var2 = var1 * var1 * self.dig_p6 / 32768.0 96 | var2 = var2 + var1 * self.dig_p5 * 2 97 | var2 = var2 / 4.0 + self.dig_p4 * 65536.0 98 | var1 = (self.dig_p3 * var1 * var1 / 524288.0 + self.dig_p2 * var1) / 524288.0 99 | var1 = (1.0 + var1 / 32768.0) * self.dig_p1 100 | pressure = 1048576.0 - raw_pressure 101 | pressure = (pressure - var2 / 4096.0) * 6250.0 / var1 102 | var1 = self.dig_p9 * pressure * pressure / 2147483648.0 103 | var2 = pressure * self.dig_p8 / 32768.0 104 | return pressure + (var1 + var2 + self.dig_p7) / 16.0 105 | 106 | def compensate_humidity(self, raw_humidity): 107 | humidity = self.temperature_fine - 76800.0 108 | humidity = (raw_humidity - (self.dig_h4 * 64.0 + self.dig_h5 / 16384.0 * humidity)) * (self.dig_h2 / 65536.0 * (1.0 + self.dig_h6 / 67108864.0 * humidity * (1.0 + self.dig_h3 / 67108864.0 * humidity))) 109 | humidity = humidity * (1.0 - self.dig_h1 * humidity / 524288.0) 110 | return max(0.0, min(100.0, humidity)) 111 | 112 | 113 | class BME280_new: 114 | def __init__(self, i2c_addr=I2C_ADDRESS_GND, i2c_dev=None): 115 | self.calibration = BME280Calibration() 116 | 117 | 118 | self._is_setup = False 119 | self._i2c_addr = i2c_addr 120 | self._i2c_dev = i2c_dev 121 | self._bme280 = Device([I2C_ADDRESS_GND, I2C_ADDRESS_VCC], i2c_dev=self._i2c_dev, bit_width=8, registers=( 122 | Register('CHIP_ID', 0xD0, fields=( 123 | BitField('id', 0xFF), 124 | )), 125 | Register('RESET', 0xE0, fields=( 126 | BitField('reset', 0xFF), 127 | )), 128 | Register('STATUS', 0xF3, fields=( 129 | BitField('measuring', 0b00001000), # 1 when conversion is running 130 | BitField('im_update', 0b00000001), # 1 when NVM data is being copied 131 | )), 132 | Register('CTRL_MEAS', 0xF4, fields=( 133 | BitField('osrs_t', 0b11100000, # Temperature oversampling 134 | adapter=LookupAdapter({ 135 | 1: 0b001, 136 | 2: 0b010, 137 | 4: 0b011, 138 | 8: 0b100, 139 | 16: 0b101 140 | })), 141 | BitField('osrs_p', 0b00011100, # Pressure oversampling 142 | adapter=LookupAdapter({ 143 | 1: 0b001, 144 | 2: 0b010, 145 | 4: 0b011, 146 | 8: 0b100, 147 | 16: 0b101})), 148 | BitField('mode', 0b00000011, # Power mode 149 | adapter=LookupAdapter({ 150 | 'sleep': 0b00, 151 | 'forced': 0b10, 152 | 'normal': 0b11})), 153 | )), 154 | Register('CTRL_HUM', 0xF2, fields=( 155 | BitField('osrs_h', 0b00000111, # Humidity oversampling 156 | adapter=LookupAdapter({ 157 | 1: 0b001, 158 | 2: 0b010, 159 | 4: 0b011, 160 | 8: 0b100, 161 | 16: 0b101})), 162 | )), 163 | Register('CONFIG', 0xF5, fields=( 164 | BitField('t_sb', 0b11100000, # Temp standby duration in normal mode 165 | adapter=LookupAdapter({ 166 | 0.5: 0b000, 167 | 62.5: 0b001, 168 | 125: 0b010, 169 | 250: 0b011, 170 | 500: 0b100, 171 | 1000: 0b101, 172 | 10: 0b110, 173 | 20: 0b111})), 174 | BitField('filter', 0b00011100), # Controls the time constant of the IIR filter 175 | BitField('spi3w_en', 0b0000001, read_only=True), # Enable 3-wire SPI interface when set to 1. IE: Don't set this bit! 176 | )), 177 | Register('DATA', 0xF7, fields=( 178 | BitField('humidity', 0x000000000000FFFF), 179 | BitField('temperature', 0x000000FFFFF00000), 180 | BitField('pressure', 0xFFFFF00000000000) 181 | ), bit_width=8 * 8), 182 | Register('CALIBRATION', 0x88, fields=( 183 | BitField('dig_t1', 0xFFFF << 16 * 12, adapter=U16Adapter()), # 0x88 0x89 184 | BitField('dig_t2', 0xFFFF << 16 * 11, adapter=S16Adapter()), # 0x8A 0x8B 185 | BitField('dig_t3', 0xFFFF << 16 * 10, adapter=S16Adapter()), # 0x8C 0x8D 186 | BitField('dig_p1', 0xFFFF << 16 * 9, adapter=U16Adapter()), # 0x8E 0x8F 187 | BitField('dig_p2', 0xFFFF << 16 * 8, adapter=S16Adapter()), # 0x90 0x91 188 | BitField('dig_p3', 0xFFFF << 16 * 7, adapter=S16Adapter()), # 0x92 0x93 189 | BitField('dig_p4', 0xFFFF << 16 * 6, adapter=S16Adapter()), # 0x94 0x95 190 | BitField('dig_p5', 0xFFFF << 16 * 5, adapter=S16Adapter()), # 0x96 0x97 191 | BitField('dig_p6', 0xFFFF << 16 * 4, adapter=S16Adapter()), # 0x98 0x99 192 | BitField('dig_p7', 0xFFFF << 16 * 3, adapter=S16Adapter()), # 0x9A 0x9B 193 | BitField('dig_p8', 0xFFFF << 16 * 2, adapter=S16Adapter()), # 0x9C 0x9D 194 | BitField('dig_p9', 0xFFFF << 16 * 1, adapter=S16Adapter()), # 0x9E 0x9F 195 | BitField('dig_h1', 0x00FF), # 0xA1 uint8 196 | ), bit_width=26 * 8), 197 | Register('CALIBRATION2', 0xE1, fields=( 198 | BitField('dig_h2', 0xFFFF0000000000, adapter=S16Adapter()), # 0xE1 0xE2 199 | BitField('dig_h3', 0x0000FF00000000), # 0xE3 uint8 200 | BitField('dig_h4', 0x000000FFFF0000, adapter=H4Adapter()), # 0xE4 0xE5[3:0] 201 | BitField('dig_h5', 0x00000000FFFF00, adapter=H5Adapter()), # 0xE5[7:4] 0xE6 202 | BitField('dig_h6', 0x000000000000FF, adapter=S8Adapter()) # 0xE7 int8 203 | ), bit_width=7 * 8) 204 | )) 205 | 206 | def setup(self, mode='normal', temperature_oversampling=16, pressure_oversampling=16, humidity_oversampling=16, temperature_standby=500): 207 | if self._is_setup: 208 | return 209 | self._is_setup = True 210 | 211 | self._bme280.select_address(self._i2c_addr) 212 | self._mode = mode 213 | 214 | 215 | if mode == "forced": 216 | mode = "sleep" 217 | 218 | try: 219 | if self._bme280.CHIP_ID.get_id() != CHIP_ID: 220 | raise RuntimeError("Unable to find bme280 on 0x{:02x}, CHIP_ID returned {:02x}".format(self._i2c_addr, self._bme280.CHIP_ID.get_id())) 221 | except IOError: 222 | raise RuntimeError("Unable to find bme280 on 0x{:02x}, IOError".format(self._i2c_addr)) 223 | 224 | self._bme280.RESET.set_reset(0xB6) 225 | time.sleep(0.1) 226 | 227 | self._bme280.CTRL_HUM.set_osrs_h(humidity_oversampling) 228 | 229 | with self._bme280.CTRL_MEAS as CTRL_MEAS: 230 | CTRL_MEAS.set_mode(mode) 231 | CTRL_MEAS.set_osrs_t(temperature_oversampling) 232 | CTRL_MEAS.set_osrs_p(pressure_oversampling) 233 | CTRL_MEAS.write() 234 | 235 | with self._bme280.CONFIG as CONFIG: 236 | CONFIG.set_t_sb(temperature_standby) 237 | CONFIG.set_filter(2) 238 | CONFIG.write() 239 | 240 | with self._bme280.CALIBRATION as CALIBRATION: 241 | self.calibration.dig_t1 = CALIBRATION.get_dig_t1() 242 | self.calibration.dig_t2 = CALIBRATION.get_dig_t2() 243 | self.calibration.dig_t3 = CALIBRATION.get_dig_t3() 244 | 245 | self.calibration.dig_p1 = CALIBRATION.get_dig_p1() 246 | self.calibration.dig_p2 = CALIBRATION.get_dig_p2() 247 | self.calibration.dig_p3 = CALIBRATION.get_dig_p3() 248 | self.calibration.dig_p4 = CALIBRATION.get_dig_p4() 249 | self.calibration.dig_p5 = CALIBRATION.get_dig_p5() 250 | self.calibration.dig_p6 = CALIBRATION.get_dig_p6() 251 | self.calibration.dig_p7 = CALIBRATION.get_dig_p7() 252 | self.calibration.dig_p8 = CALIBRATION.get_dig_p8() 253 | self.calibration.dig_p9 = CALIBRATION.get_dig_p9() 254 | 255 | self.calibration.dig_h1 = CALIBRATION.get_dig_h1() 256 | 257 | with self._bme280.CALIBRATION2 as CALIBRATION: 258 | self.calibration.dig_h2 = CALIBRATION.get_dig_h2() 259 | self.calibration.dig_h3 = CALIBRATION.get_dig_h3() 260 | self.calibration.dig_h4 = CALIBRATION.get_dig_h4() 261 | self.calibration.dig_h5 = CALIBRATION.get_dig_h5() 262 | self.calibration.dig_h6 = CALIBRATION.get_dig_h6() 263 | 264 | 265 | 266 | def update_sensor(self): 267 | self.setup() 268 | 269 | if self._mode == "forced": 270 | # Trigger a reading in forced mode and wait for result 271 | self._bme280.CTRL_MEAS.set_mode("forced") 272 | while self._bme280.STATUS.get_measuring(): 273 | time.sleep(0.001) 274 | 275 | with self._bme280.DATA as DATA: 276 | raw_temperature = DATA.get_temperature() 277 | raw_pressure = DATA.get_pressure() 278 | raw_humidity = DATA.get_humidity() 279 | 280 | self.temperature = self.calibration.compensate_temperature(raw_temperature) 281 | self.pressure = self.calibration.compensate_pressure(raw_pressure) / 100.0 282 | self.humidity = self.calibration.compensate_humidity(raw_humidity) 283 | return (self.temperature, self.pressure, self.humidity) # TheHWcave: added return values 284 | 285 | def get_temperature(self): 286 | self.update_sensor() 287 | return self.temperature 288 | 289 | def get_pressure(self): 290 | self.update_sensor() 291 | return self.pressure 292 | 293 | def get_humidity(self): 294 | self.update_sensor() 295 | return self.humidity 296 | 297 | def get_altitude(self, qnh=1013.25): 298 | self.update_sensor() 299 | pressure = self.get_pressure() 300 | altitude = 44330.0 * (1.0 - pow(pressure / qnh, (1.0 / 5.255))) 301 | return altitude 302 | --------------------------------------------------------------------------------