Nachdem wir in Teil 1 ja schon die Daten eines BME280 Sensors (Temperatur, Luftdruck, Luftfeuchte) von einem Raspberry Pi aus ins Internet übertragen und schön bunt auswerten, folgt nun Teil 2: Diesmal nehmen wir einen sehr günstigen ESP8266 Microcontroller (~3 Euro) mit Micropython!
Zuallererst kaufen wir uns ein fertiges ESP8266 Board – das hat neben dem ESP8266 noch einen USB-Seriell Wandler, einen (oft stromfressenden!) Step-Down Converter (von 5V USB auf 3.3V für den ESP8266), eine LED und einen USB-Anschluß mit dabei. Testen werde ich hier zwei Stück, und zwar:
NodeMCU Lua Amica Modul V2 ESP8266 ESP-12E Wifi Wifi Development Board mit CP2102
D1 Mini NodeMcu mit ESP8266-12E WLAN Module für Arduino, 100% WeMos kompatibel
Beide Boards kosten 5.99€ (wenn man ein paar Wochen Zeit hat kann man sie bei aliexpress für die Hälfte bestellen!) und der große Unterschied ist dass man beim Wemos die Steckerleisten selbst anlöten muss. Dafür hat das Wemos einen deutlich stromsparenderen Step-Down Converter.
Als allererstes müssen wir mal Micropython auf die beiden Boards bringen. Downloaden können wir das hier: micropython.org
Dann brauchen wir noch python3, pip3, esptool und rshell – unter (Ubuntu) Linux sollten die folgenden Befehle hier schnell zum Ziel führen:
apt-get install python3 python3-pip pip3 install esptool rshell
Nachdem wir das gemacht haben stecken wir das Board unsere Wahl an den Computer an. Mittels ‚dmesg‘ sollten wir sehen welchen Anschluß es zugewiesen bekommt. Bei mir ist es eigentlich immer ‚/dev/ttyUSB0‘ gewesen.
Wenn wir nun probieren zuerst den Speicher zu leeren und danach Micropython aufzuspielen so können wir das nur wenn das Board im Bootloader Modus ist. Beides Boards versuchen diesen Modus automatisch zu aktivieren wenn man Kommandos überträgt (die gleiche Technik wie bei Arduino) aber das klappt nicht unbedingt immer. Deshalb muss man beim NodeMCU Board den FLASH Button halten und danach das Board neu anstecken oder mittels Reset-Button neu starten. Dann kann man den Flash Button loslassen denn man ist nun im Bootloader Modus. Beim Wemos ist es noch einen Tick komplizierter denn hier gibt es einfach mal keinen Flash Button. Statt dem Flash Button muss man einfach den Pin GPI0/D0 mit einem kleinen Kabel mit GND verbinden!
Nun zum flashen: Diese beiden Befehle leeren erst den Flash-Speicher des ESP8266 und übertragen dann Micropython drauf:
esptool.py --port /dev/ttyUSB0 erase_flash esptool.py --port /dev/ttyUSB0 write_flash -fs 4MB -fm dout 0x0 /home/ingres/esp8266-20171101-v1.9.3.bin
Nun können wir uns entweder ganz normal mittels Putty oder Screen auf die REPL von Micropython verbinden, oder aber wir nehmen ‚rshell‘, welches den Vorteil hat dass es auch Dateien übertragen kann. Und da Micropython per Default die Datei ‚main.py‘ automatisch beim Starten des Boards startet müssen wir unser Programm nur als ‚main.py‘ übertragen. So einfach 🙂
Hier also der Befehl um rshell zu starten und die main.py auf das Board zu senden. Mittels ‚repl‘ kommt man von rshell aus auf die Micropython Kommandokonsole. Mit Strg+X kommt man wieder aus der REPL raus.
rshell --buffer-size=30 -p /dev/ttyUSB0 cp main.py /pyboard
Das einzige was jetzt noch fehlt ist das Programm ‚main.py‘ und die richtige Verkabelung. Hier also main.py:
# Using parts of the following scripts: # 1. https://www.raspberrypi-spy.co.uk/2016/07/using-bme280-i2c-temperature-pressure-sensor-in-python/ # 2. http://ecorov.com/2017/08/03/i2c-gy-91.html import time import ustruct import machine import network import usocket # Globals debug = 1 sensorid = 3 sensoraddr = 0x76 sendseconds = 60 secondstrywificonnect = 10 REG_DATA = 0xF7 REG_CONTROL = 0xF4 REG_CONFIG = 0xF5 REG_CONTROL_HUM = 0xF2 REG_HUM_MSB = 0xFD REG_HUM_LSB = 0xFE OVERSAMPLE_TEMP = 2 OVERSAMPLE_PRES = 2 MODE = 1 if debug: print(str(time.ticks_ms()) + ': ***STARTUP***') print(str(time.ticks_ms()) + ': sensorid: ' + str(sensorid)) print(str(time.ticks_ms()) + ': sensoraddr: ' + str(sensoraddr)) print(str(time.ticks_ms()) + ': sendseconds: ' + str(sendseconds)) print(str(time.ticks_ms()) + ': secondstrywificonnect: ' + str(secondstrywificonnect)) print(str(time.ticks_ms()) + ': REG_DATA: ' + str(REG_DATA)) print(str(time.ticks_ms()) + ': REG_CONTROL: ' + str(REG_CONTROL)) print(str(time.ticks_ms()) + ': REG_CONFIG: ' + str(REG_CONFIG)) print(str(time.ticks_ms()) + ': REG_CONTROL_HUM: ' + str(REG_CONTROL_HUM)) print(str(time.ticks_ms()) + ': REG_HUM_MSB: ' + str(REG_HUM_MSB)) print(str(time.ticks_ms()) + ': REG_HUM_LSB: ' + str(REG_HUM_LSB)) print(str(time.ticks_ms()) + ': OVERSAMPLE_TEMP: ' + str(OVERSAMPLE_TEMP)) print(str(time.ticks_ms()) + ': OVERSAMPLE_PRES: ' + str(OVERSAMPLE_PRES)) print(str(time.ticks_ms()) + ': MODE: ' + str(MODE)) # Activate a time which will reset us in sendseconds if we're hanging somewhere! if debug: print(str(time.ticks_ms()) + ': Activating Timer') tim = machine.Timer(-1) tim.init(period = sendseconds * 1000, mode = machine.Timer.ONE_SHOT, callback = lambda t:machine.reset()) # Activate the internal RTC (we don't need the time, but we want to know when a minute has passed!) if debug: print(str(time.ticks_ms()) + ': Activating RTC') rtc = machine.RTC() # create a RTC alarm that expires after 60 seconds rtc.irq(trigger = rtc.ALARM0, wake = machine.DEEPSLEEP) rtc.alarm(rtc.ALARM0, sendseconds * 1000) if debug: print(str(time.ticks_ms()) + ': RTC Time now: ' + '{:02}'.format(rtc.datetime()[2]) + '.' + '{:02}'.format(rtc.datetime()[1]) + '.' + '{:04}'.format(rtc.datetime()[0]) + ' ' + '{:02}'.format(rtc.datetime()[4]) + ':' + '{:02}'.format(rtc.datetime()[5]) + ':' + '{:02}'.format(rtc.datetime()[6])) # Any exception will send us into deepsleep (where our rtc will wake us!) try: if debug: print(str(time.ticks_ms()) + ': Trying to connect') # Connect to WiFi -> Get interfaces sta_if = network.WLAN(network.STA_IF) ap_if = network.WLAN(network.AP_IF) # Deactivate access point, we're station only ap_if.active(False) # Now connect connectcount = 0 if not sta_if.isconnected(): sta_if.active(True) sta_if.connect('myWifi', 'myPass') while not sta_if.isconnected(): connectcount = connectcount + 1 time.sleep(1) if debug: print(str(time.ticks_ms()) + ': Connect try: ' + str(connectcount)) if connectcount > secondstrywificonnect: # We didn't connect after secondstrywificonnect seconds. Let's sleep if debug: print(str(time.ticks_ms()) + ': Connect failed after ' + str(connectcount) + ' seconds sleep. Going to deepsleep') machine.deepsleep() # Startup I2C if debug: print(str(time.ticks_ms()) + ': Starting I2C') i2c = machine.I2C(scl = machine.Pin(5), sda = machine.Pin(4), freq = 100000) # Set Oversampling if debug: print(str(time.ticks_ms()) + ': Setting BME280 Oversampling') OVERSAMPLE_HUM = 2 i2c.writeto_mem(sensoraddr, REG_CONTROL_HUM, bytearray([OVERSAMPLE_HUM])) control = OVERSAMPLE_TEMP << 5 | OVERSAMPLE_PRES << 2 | MODE i2c.writeto_mem(sensoraddr, REG_CONTROL, bytearray([control])) # Read Calibration data from sensor if debug: print(str(time.ticks_ms()) + ': Getting BME280 Calibration data') cal1 = i2c.readfrom_mem(sensoraddr, 0x88, 24) cal2 = i2c.readfrom_mem(sensoraddr, 0xA1, 1) cal3 = i2c.readfrom_mem(sensoraddr, 0xE1, 7) # Convert bytes to words dig_T1 = ustruct.unpack('H', cal1[0:2])[0] dig_T2 = ustruct.unpack('h', cal1[2:4])[0] dig_T3 = ustruct.unpack('h', cal1[4:6])[0] dig_P1 = ustruct.unpack('H', cal1[6:8])[0] dig_P2 = ustruct.unpack('h', cal1[8:10])[0] dig_P3 = ustruct.unpack('h', cal1[10:12])[0] dig_P4 = ustruct.unpack('h', cal1[12:14])[0] dig_P5 = ustruct.unpack('h', cal1[14:16])[0] dig_P6 = ustruct.unpack('h', cal1[16:18])[0] dig_P7 = ustruct.unpack('h', cal1[18:20])[0] dig_P8 = ustruct.unpack('h', cal1[20:22])[0] dig_P9 = ustruct.unpack('h', cal1[22:24])[0] dig_H1 = ustruct.unpack('B', cal2[0:1])[0] dig_H2 = ustruct.unpack('h', cal3[0:2])[0] dig_H3 = ustruct.unpack('B', cal3[2:3])[0] dig_H4 = ustruct.unpack('b', cal3[3:4])[0] dig_H4 = (dig_H4 << 24) >> 20 dig_H4 = dig_H4 | (ustruct.unpack('b', cal3[4:5])[0] & 0x0F) dig_H5 = ustruct.unpack('b', cal3[5:6])[0] dig_H5 = (dig_H5 << 24) >> 20 dig_H5 = dig_H5 | (ustruct.unpack('B', cal3[4:5])[0] >> 4 & 0x0F) dig_H6 = ustruct.unpack('b', cal3[6:7])[0] # Wait in ms (Datasheet Appendix B: Measurement time and current calculation) if debug: print(str(time.ticks_ms()) + ': Sleeping for BME280 to be ready') wait_time = 1.25 + (2.3 * OVERSAMPLE_TEMP) + ((2.3 * OVERSAMPLE_PRES) + 0.575) + ((2.3 * OVERSAMPLE_HUM)+0.575) time.sleep(wait_time/1000) # Read temperature/pressure/humidity if debug: print(str(time.ticks_ms()) + ': Reading BME280') data = i2c.readfrom_mem(sensoraddr, REG_DATA, 8) pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4) temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4) hum_raw = (data[6] << 8) | data[7] # Refine temperature if debug: print(str(time.ticks_ms()) + ': Refine temperature') var1 = ((((temp_raw>>3)-(dig_T1<<1)))*(dig_T2)) >> 11 var2 = (((((temp_raw>>4) - (dig_T1)) * ((temp_raw>>4) - (dig_T1))) >> 12) * (dig_T3)) >> 14 t_fine = var1 + var2 temperature = float(((t_fine * 5) + 128) >> 8) # Refine pressure and adjust for temperature if debug: print(str(time.ticks_ms()) + ': Refine pressure') var1 = t_fine / 2.0 - 64000.0 var2 = var1 * var1 * dig_P6 / 32768.0 var2 = var2 + var1 * dig_P5 * 2.0 var2 = var2 / 4.0 + dig_P4 * 65536.0 var1 = (dig_P3 * var1 * var1 / 524288.0 + dig_P2 * var1) / 524288.0 var1 = (1.0 + var1 / 32768.0) * dig_P1 if var1 == 0: pressure = 0 else: pressure = 1048576.0 - pres_raw pressure = ((pressure - var2 / 4096.0) * 6250.0) / var1 var1 = dig_P9 * pressure * pressure / 2147483648.0 var2 = pressure * dig_P8 / 32768.0 pressure = pressure + (var1 + var2 + dig_P7) / 16.0 # Refine humidity if debug: print(str(time.ticks_ms()) + ': Refine humidity') humidity = t_fine - 76800.0 humidity = (hum_raw - (dig_H4 * 64.0 + dig_H5 / 16384.0 * humidity)) * (dig_H2 / 65536.0 * (1.0 + dig_H6 / 67108864.0 * humidity * (1.0 + dig_H3 / 67108864.0 * humidity))) humidity = humidity * (1.0 - dig_H1 * humidity / 524288.0) if humidity > 100: humidity = 100 elif humidity < 0: humidity = 0 # Data is ready temperature = temperature / 100.0 pressure = pressure / 100.0 humidity = humidity if debug: print(str(time.ticks_ms()) + ': Data fetched: temp ' + str(temperature) + ' - press ' + str(pressure) + ' - hum ' + str(humidity)) # Send data to the Internet, a Post Request with http - we don't use SSL here! content = b'sensorid=' + str(sensorid) + '&temp=' + str(temperature) + '&press=' + str(pressure) + '&hum=' + str(humidity) + '&password=zzz' if debug: print(str(time.ticks_ms()) + ': Connecting to website') addr_info = usocket.getaddrinfo("meinserver.de", 80) addr = addr_info[0][-1] sock = usocket.socket() sock.connect(addr) sock.send(b'POST /tempsensor.php HTTP/1.1\r\n') sock.send(b'Host: meinserver.de\r\n') sock.send(b'Content-Type: application/x-www-form-urlencoded\r\n') sock.send(b'Content-Length: ' + str(len(content)) + '\r\n') sock.send(b'\r\n') if debug: print(str(time.ticks_ms()) + ': Sending: ' + str(content)) sock.send(content) sock.send(b'\r\n\r\n') if debug: print(str(time.ticks_ms()) + ': Answer: ' + str(sock.recv(1000))) sock.close() # Go to deep sleep (Device starts from reset again!) - Make sure that ESP8266 GPIO16 is connected to the RST Pin! if debug: print(str(time.ticks_ms()) + ': All done. DEEPSLEEP') machine.deepsleep() except: if debug: print(str(time.ticks_ms()) + ': Exception happend. DEEPSLEEP') machine.deepsleep()
Der Einfachheit halber gibts die Datei hier zum runterladen: main.py
Auf dem Server nehmen wir unser bekanntes PHP-Script ‚tempsensor.php‘ von Teil1. Die Datenbank ist dort auch beschrieben. Die Sensorid für den neuen Sensor 2 können wir wie folgt hinzufügen:
INSERT INTO `sensors` (`id`, `name`, `description`) VALUES (2, 'outside living room', 'on the terrace right before the window to my computer');
Nun noch die Anschlüsse:
- Damit der ESP8266 aus dem Deepsleep aufwacht muss der WAKE-Pin (D0) mit dem RST Pin fest verbunden werden.
- VCC vom BME280 an den 3.3V Pin
- GND vom BME280 an den GND Pin
- SCL vom BME280 an D1
- SDA vom BME280 an D2
Fertig! So einfach 🙂
Was ich nun noch bieten kann sind einmal die Debug-Ausgaben mit Timing, damit man weiß wie lange die Phase ist in der der ESP8266 viel Strom zieht und wie lange er im Deepsleep ist, dies hier ist einmal ein kompletter Neustart und ein Start wo das WLAN schonmal connected war und deshalb schneller connected ist:
(Um diese Debug-Ausgaben zu bekommen muss man das Board einfach an den Computer anstecken und auf dem seriellen Port per Putty oder screen oder minicom hören. Übrigens geht das neu übertragen von Programmcode per rshell nicht mehr da das Programm ja durchgehend läuft und deshalb die rshell Kommandos nicht durchkommen. Man muss also Micropython neu flashen wenn man die main.py austauschen möchte!)
956: ***STARTUP*** 960: sensorid: 3 965: sensoraddr: 118 970: sendseconds: 60 975: secondstrywificonnect: 10 980: REG_DATA: 247 985: REG_CONTROL: 244 990: REG_CONFIG: 245 995: REG_CONTROL_HUM: 242 1000: REG_HUM_MSB: 253 1005: REG_HUM_LSB: 254 1010: OVERSAMPLE_TEMP: 2 1015: OVERSAMPLE_PRES: 2 1020: MODE: 1 1025: Activating Timer 1029: Activating RTC 1034: RTC Time now: 07.10.2018 18:21:55 1062: Trying to connect #5 ets_task(4020f474, 28, 3fff8df8, 10) 2229: Connect try: 1 3237: Connect try: 2 4292: Connect try: 3 5300: Connect try: 4 6309: Connect try: 5 6318: Starting I2C 6322: Setting BME280 Oversampling 6327: Getting BME280 Calibration data 6343: Sleeping for BME280 to be ready 6363: Reading BME280 6369: Refine temperature 6373: Refine pressure 6377: Refine humidity 6381: Data fetched: temp 23.76 - press 965.002 - hum 46.4372 6399: Connecting to website 6486: Sending: b'sensorid=3&temp=23.76&press=965.002&hum=46.4372&password=zzz' 6492: Answer: b'HTTP/1.1 200 OK\r\nDate: Sun, 07 Oct 2018 16:23:28 GMT\r\nServer: Apache\r\nContent-Length: 5\r\nContent-Type: text/html; charset=UTF-8\r\n\r\nDone.' 6768: All done. DEEPSLEEP 948: ***STARTUP*** 952: sensorid: 3 957: sensoraddr: 118 963: sendseconds: 60 968: secondstrywificonnect: 10 973: REG_DATA: 247 978: REG_CONTROL: 244 983: REG_CONFIG: 245 988: REG_CONTROL_HUM: 242 993: REG_HUM_MSB: 253 997: REG_HUM_LSB: 254 1002: OVERSAMPLE_TEMP: 2 1007: OVERSAMPLE_PRES: 2 1012: MODE: 1 1017: Activating Timer 1021: Activating RTC 1025: RTC Time now: 07.10.2018 18:22:56 1053: Trying to connect 2059: Connect try: 1 3067: Connect try: 2 4075: Connect try: 3 4082: Starting I2C 4086: Setting BME280 Oversampling 4094: Getting BME280 Calibration data 4110: Sleeping for BME280 to be ready 4130: Reading BME280 4136: Refine temperature 4141: Refine pressure 4145: Refine humidity 4149: Data fetched: temp 23.68 - press 965.0 - hum 46.6988 4167: Connecting to website 4227: Sending: b'sensorid=3&temp=23.68&press=965.0&hum=46.6988&password=zzz' 4233: Answer: b'HTTP/1.1 200 OK\r\nDate: Sun, 07 Oct 2018 16:24:24 GMT\r\nServer: Apache\r\nContent-Length: 5\r\nContent-Type: text/html; charset=UTF-8\r\n\r\nDone.' 4444: All done. DEEPSLEEP
Grob können wir also sagen dass wir 6 Sekunden High Power haben und danach ca. 54 Sekunden Deepsleep. Während der High Power Phase brauchen beide Boards ca. 55 mA mit bis zu 600 mA Peak. Das NodeMCU sleept mit 1.3 mA, das Wemos D1 Mini mit 175 µA. Senden wir alle 10 Minuten so ist das ein durchschnittlicher Stromverbrauch (bei 5V) von 1.837 mA beim NodeMCU und 0.723 mA beim Wemos. Eine (theoretische) 5V Batterie mit 2000 mAh würde also beim NodeMCU für ca. 1088 Stunden halten, beim Wemos für ca. 2766 Stunden, also 45 Tage oder 115 Tage.
Mit der theoretischen 5V, 2000 mAh Batterie meine ich übrigens wenn man z.B. 4x 1.2V Akkus (oder 1.5V Batterien) mit ca. 2000 mAh Stunden hintereinander schaltet. So erhält man bei Batterien irgendwas zwischen 6.8V und 4.8V, bei Akkus zwischen 5.5V und 4V mit eben jenen 2000 mAh.
Hier meine Berechnungen, grob überschlagen aber hoffentlich grob richtig:
Alle 10 Minuten senden beim NodeMCU: 00.6 Sekunden 55 mA -> 0.06 Sekunden mit 0,275 Watt 59.4 Sekunden 1.3 mA -> 59.4 Sekunden mit 0,0065 Watt -> ((55 * 0,6) + (59,4 * 1,3)) / 60 (33 + 77,22) / 60 => 1.837 mA => 0.09185 Watt Alle 10 Minuten senden beim Wemos D1: 00.6 Sekunden 55 mA -> 0.06 Sekunden mit 0,275 Watt 59.4 Sekunden 0.175 mA -> 59.4 Sekunden mit 0,000875 Watt -> ((55 * 0,6) + (59,4 * 0,175)) / 60 (33 + 10,395) / 60 => 0.72325 mA => 0.00361625 Watt
Und hier gibt es jetzt noch ein hübsches Bild der Messungen der letzten drei Tage innen und außen:
In Teil 3 schauen wir übrigens meinem Stromzähler genau auf die Finger!
Schreibe einen Kommentar