Skip to content

Commit

Permalink
Update to v2.5 (stable release!?)
Browse files Browse the repository at this point in the history
Months of trouble.. but this is a stable release without anymore bugs!
  • Loading branch information
StevenCellist committed Aug 16, 2022
1 parent 5bb63a6 commit 081984d
Show file tree
Hide file tree
Showing 19 changed files with 922 additions and 975 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ __pycache__/
*.py[cod]
*$py.class

pymakr.conf
pymakr.conf

**secret.py
42 changes: 17 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# Meet je leefomgeving
In deze repository vindt u de meest recente software die op de meetkastjes gebruikt wordt.
Het prototype hiervan is ontworpen door Klaas van Laveren in samenwerking met ICR3ATE en vervolgens volledig verder ontwikkeld door Steven Boonstoppel.
In deze repository is de meest recente software te vinden die op de meetkastjes gebruikt wordt.
Het prototype hiervan is ontworpen door Klaas van Laveren in samenwerking met ICR3ATE en vervolgens volledig doorontwikkeld door Steven Boonstoppel.

## Algemene opzet
De constructie van de software is als volgt:
Bij het opstarten wordt <kbd>boot.py</kbd> uitgevoerd. Als allereerste wordt daar de LED en WiFi onderdrukt.
Daarna wordt <kbd>main.py</kbd> uitgevoerd. Vervolgens wordt de GPS module geactiveerd als op de externe knop is gedrukt door de gebruiker. Deze probeert om een locatie te krijgen; zodra dat gelukt is wordt de module weer uitgeschakeld.
ls het kastje uit slaapstand opgewaakt is, betekent dat vervolgens dat er meetwaarden van de vorige ronde aanwezig zijn. Deze worden uit RAM gehaald. Vervolgens wordt LoRa opgestart. Ook hiervan kan informatie uit RAM gehaald worden als het kastje uit slaapstand komt. Is dit niet het geval, dan probeert het kastje om het netwerk te joinen. Waarden van een eventuele vorige ronde worden nu verzonden over LoRa, waarna de LoRa informatie weer wordt opgeslagen in RAM voor de volgende sessie.
Dan starten de SDS011 en MQ135 die beiden 30 seconden tijd nodig hebben om hun metingen te stabiliseren. Terwijl deze warmdraaien, worden voor de gebruiker de waarden die zojuist verzonden zijn ook op het display weergegeven. Tijdens dit deel van de software wordt gebruik gemaakt van *lightsleep*: een efficiënte slaapstand waarbij alle informatie intact blijft. Zodra de 30 seconden voorbij zijn worden beide sensoren gemeten en in slaapstand gezet.
Vervolgens worden alle anderen sensoren gestart en worden hun relevante waarden gemeten, waarna ook zij weer in slaapstand gezet worden. Dan worden alle waarden opgeslagen in RAM, waarna het kastje in *deepsleep* gaat. De waarden van deze sessie worden pas tijdens de volgende verzonden.
Wordt tijdens deepsleep op de knop gedrukt, dan waakt het kastje op en wordt de GPS-module geactiveerd om de locatie vast te zetten. Aangezien dit slechts bij verplaatsing hoeft te gebeuren, wordt dit ook maar eenmalig uitgevoerd. En zo zijn we weer vooraan teruggekomen.
De focus van de kastjes ligt uiteraard op uithoudingsvermogen. Praktisch betekent dat dat elke sensor zo kort mogelijk actief is en de stroomsterkte geminimaliseerd is.
Met dat doel voor ogen is de volgende constructie opgezet:
De kastjes worden zes keer per uur 'wakker' uit een diepe slaapstand. Twee keer daarvan worden alle sensoren (behalve GPS) gebruikt, dus inclusief de CO2- en fijnstofsensoren. Deze laatste twee sensoren moeten beide circa 30 seconden actief zijn om een goede meetwaarde te genereren. Om te voorkomen dat deze sensoren 'voor niets' meten, worden de berichtjes bij deze twee sessies op maximale zendkracht verzonden: SF12.
De andere vier keer dat de kastjes wakker worden, staan ze zo kort mogelijk aan: de CO2- en fijnstofsensoren worden dus niet ingeschakeld (net als GPS). Omdat er niet altijd op SF12 verzonden mag worden (zie verderop), worden deze berichtjes verzonden op SF10. Het is mooi meegenomen als deze berichtjes aankomen, maar niet al te erg als dat niet gebeurt.
Eén keer per dag wordt de GPS-module geactiveerd. De kastjes zullen nauwelijks verplaatsen, dus is één keer per dag afdoende. Soms is het GPS-bereik echter vrij slecht vanwege obstakels, dus wordt er een timeout gebruikt van 120 seconden: wordt er binnen die tijd geen locatie gevonden, schakelt GPS weer uit.

Zodra een meetcycles voltooid is toont het kastje eerst de gemeten waarden op het display waarbij gebruik gemaakt wordt van een *lightsleep* met verminderd stroomverbruik; daarna gaat het kastje in *deepsleep* waarbij nagenoeg alle componenten uitgeschakeld zijn: alleen de drukknop aan de zijkant van het kastje wordt nog gemonitord. Wordt die knop ingedrukt, dan wordt er een geforceerde meting op SF12 uitgevoerd. Dit helpt bijvoorbeeld bij het debuggen of testen van bereik, of bij bepaalde opdrachten waarbij leerlingen vaker een meting zouden willen doen dan het standaard-interval van 10 minuten.

## LoRa, The Things Network en Cayenne Low Power Payload
De data van de kastjes wordt verzonden via het LoRa (Long Range) protocol. De kastjes fungeren als *end node* en communiceren met de antennes bovenop de Veense middelbare scholen (en eventueel andere actieve binnen het bereik). Daarvoor kan gebruik gemaakt worden van verschillende data-rates met hun eigen voordelen.
De antennes en daarmee de kastjes zijn aangesloten op het The Things Network (TTN). Deze ondersteunt standaard SF9 of SF12 (respectievelijk data rates 3 en 0). Hoe lager de data rate, hoe groter het bereik. SF7 en SF8 zijn gelimiteerd tot 235 bytes, SF9 tot 128 bytes, en SF10 t/m SF12 tot 51 bytes. Helaas is het niet toegestaan om hardcoded alleen gebruik te maken van SF11 en/of SF12; apparaten die dit gebruik worden pro-actief geblokkeerd. Hoe hoger de Spreading Factor, hoe groter het bereik en hoe meer airtime en stroom het kost om de berichten te versturen. [Achtergrondinformatie](https://www.thethingsnetwork.org/forum/t/fair-use-policy-explained/1300).
Voor het versturen van de LoRa berichten wordt gebruik gemaakt van CayenneLPP. Deze library ondersteunt op een compacte en efficiënte manier een aantal datatypen en geeft via myDevices een mooie webcompanion (webhook integratie nodig op TTN) waar alle data in figuren uit te lezen is met 30 dagen dataretention. In de huidige configuratie is de payload size van de CayenneLPP berichten 43 of 54 bytes afhankelijk van wel of geen GPS (12 channel bytes, 12 sensortype bytes en 30 databytes). Dit maakt het gebruik van SF10 en hoger onmogelijk, dus houden we voor nu SF9 aan. Mogelijkheden om te verhogen naar SF10 en misschien SF11 of SF12 worden onderzocht, maar kunnen niet zomaar ingesteld worden.
De data van de kastjes wordt verzonden via het LoRa (Long Range) protocol. De kastjes fungeren als *end node* en communiceren met de antennes bovenop het Ichthus College en eventuele andere antennes in de omgeving (Scherpenzeel, Aalst, ..). Daarvoor kan gebruik gemaakt worden van verschillende data-rates met hun eigen voordelen.
De antennes en daarmee de kastjes zijn aangesloten op het The Things Network (TTN). Deze ondersteunt standaard SF7 t/m SF12 (respectievelijk data rates 5 t/m 0). Hoe lager de data rate, hoe groter het bereik. SF7 en SF8 zijn gelimiteerd tot 235 bytes, SF9 tot 128 bytes, en SF10 t/m SF12 tot 51 bytes. Helaas is het niet toegestaan om hardcoded alleen gebruik te maken van SF11 en/of SF12; apparaten die dit gebruik worden pro-actief geblokkeerd. Hoe hoger de Spreading Factor, hoe groter het bereik en hoe meer airtime en stroom het kost om de berichten te versturen. [Achtergrondinformatie](https://www.thethingsnetwork.org/forum/t/fair-use-policy-explained/1300).
Voor het versturen van de LoRa berichten wordt gebruik gemaakt van CayenneLPP. Deze library ondersteunt op een compacte en efficiënte manier een aantal datatypen en geeft via myDevices een mooie webcompanion (webhook integratie nodig op TTN) waar alle data in figuren uit te lezen is met 30 dagen dataretention. In de huidige configuratie is de payload size van de CayenneLPP berichten 30, 41 of 42 bytes afhankelijk van de actieve componenten. Zodoende is gekozen om SF10 en SF12 te gebruiken.

## Hardware
Microcontroller: [Pycom LoPy4](https://pycom.io/product/lopy4/) op [Expansion Board v3(.1)](https://pycom.io/product/expansion-board-3-0/)
Expand All @@ -40,19 +40,11 @@ Let op: versie 3.1 van dit breakout board verschilt op meer vlakken van v3.0 dan
[Voltage divider: mess](https://community.hiveeyes.org/t/batterieuberwachung-voltage-divider-und-attenuation-fur-micropython-firmware/2128/46?page=2)

## Stroomgebruik en spanning
Zie de figuur hieronder voor het stroomgebruik van de huidige versie software. De gemiddelde stroomsterkte tijdens activiteit is 105 mA; in deepsleep 1.6 mA.
De vermoedde accuduur is (ruim) drie weken, waarbij het zonnepaneel buiten beschouwing wordt gelaten.
***Verouderd: v2.0 i.t.t. huidige v2.5***
Zie de figuur hieronder voor het stroomgebruik van de vorige versie software. De gemiddelde stroomsterkte tijdens activiteit is 105 mA; in deepsleep 3.4 mA.
De vermoedde accuduur is drie weken, waarbij het zonnepaneel buiten beschouwing wordt gelaten.
![Stroomgebruik MJLO-12 op v19.01.22](Stroomgebruik_v19_01_22.png)

## Schema
Zie [dit bestand](Schematic_Meet_je_leefomgeving_2022-01-26.pdf) voor de opbouw van het circuit in de sensorkastjes.

## Libraries
De libraries in deze repository zijn overgenomen van bestaande Circuit- of MicroPython en vervolgens zo ver als mogelijk gestript om de import-tijden beperkt te houden. CircuitPython libraries zijn waar nodig omgebouwd om met MicroPython te kunnen gebruiken.
[SSD1306 MicroPython](https://github.com/adafruit/micropython-adafruit-ssd1306/blob/master/ssd1306.py)
[VEML6070 CircuitPython](https://github.com/adafruit/Adafruit_CircuitPython_VEML6070/blob/main/adafruit_veml6070.py)
[TSL2591 CircuitPython](https://github.com/adafruit/Adafruit_CircuitPython_TSL2591/blob/main/adafruit_tsl2591.py)
[BME680 CircuitPython](https://github.com/adafruit/Adafruit_CircuitPython_BME680/blob/main/adafruit_bme680.py)
[MQ135 MicroPython](https://github.com/rubfi/MQ135/blob/master/mq135.py)
[SDS011 MicroPython](https://github.com/alexmrqt/micropython-sds011/blob/master/sds011.py)
[GPS decoder MicroPython](https://github.com/inmcm/micropyGPS/blob/master/micropyGPS.py)
Zie de figuur voor de opbouw van het circuit in de sensorkastjes.
![Schematic v2.5 15-08-2022](Schematic_Meet_je_leefomgeving_2022-08-15.svg)
Binary file not shown.
5 changes: 5 additions & 0 deletions Schematic_Meet_je_leefomgeving_2022-08-15.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions software/collect_gps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import machine
import time

def run_gps(timeout = 120):
from lib.micropyGPS import MicropyGPS

values = {}

gps_en = machine.Pin('P22', mode = machine.Pin.OUT) # 2N2907 (PNP) gate pin
gps_en.hold(False) # disable hold from deepsleep
gps_en.value(0) # enable GPS power
gps = MicropyGPS() # create GPS object

com = machine.UART(2, pins = ('P3', 'P4'), baudrate = 9600) # GPS communication

t1 = time.time()
while gps.latitude == gps.longitude == 0 and time.time() - t1 < timeout: # timeout if no fix after ..
while com.any(): # wait for incoming communication
my_sentence = com.readline() # read NMEA sentence
for x in my_sentence:
gps.update(chr(x)) # decode it through micropyGPS

gps_en.value(1)
gps_en.hold(True)

values["lat"] = gps.latitude
values["long"] = gps.longitude
values["alt"] = gps.alt
return values
72 changes: 72 additions & 0 deletions software/collect_sensors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import machine
import time

from lib.VEML6070 import VEML6070
from lib.TSL2591 import TSL2591
from lib.BME680 import BME680
from lib.MAX4466 import MAX4466
from lib.KP26650 import KP26650

def run_collection(i2c_bus, all_sensors, t_wake = 30):

values = {} # collection of all values, to be returned

bme680 = BME680(i2c = i2c_bus, address = 0x77)
bme680.set_gas_heater_temperature(400, nb_profile = 1) # set VOC plate heating temperature
bme680.set_gas_heater_duration(50, nb_profile = 1) # set VOC plate heating duration
bme680.select_gas_heater_profile(1) # select those settings
while not bme680.get_sensor_data():
time.sleep_ms(10)
values['temp'] = bme680.temperature
values['pres'] = bme680.pressure
values['humi'] = bme680.humidity
values['voc'] = bme680.gas
bme680.set_power_mode(0)

veml6070 = VEML6070(i2c = i2c_bus, address = 56) # UV sensor (0.4 / 0.0 mA) (0x38)
veml6070.wake()
time.sleep(0.2) # sensor stabilization time (required!!)
values['uv'] = veml6070.uv_raw
values['uv'] = veml6070.uv_raw # first poll may fail so do it twice
veml6070.sleep()

tsl2591 = TSL2591(i2c = i2c_bus, address = 41) # lux sensor (0.4 / 0.0 mA) (0x29)
tsl2591.wake()
time.sleep(0.2) # sensor stabilization time (required!!)
values['lx'] = tsl2591.lux
tsl2591.sleep()

max4466 = MAX4466('P15', duration = 200) # analog loudness sensor (200ms measurement)
values['volu'] = max4466.get_volume() # active: 0.3 mA, sleep: 0.3 mA (always on)

battery = KP26650('P16', duration = 50, ratio = 2) # battery voltage (50ms measurement, 1:1 voltage divider)
values['volt'] = battery.get_voltage()
values['perc'] = battery.get_percentage(values['volt'], lb = 2.9, ub = 4.1) # map voltage from 2.9..4.1 V to 0..100%

if all_sensors == True:
from lib.SDS011 import SDS011
from lib.MQ135 import MQ135

regulator = machine.Pin('P21', mode = machine.Pin.OUT) # voltage regulator SHDN pin
regulator.hold(False) # disable hold from deepsleep
regulator.value(1) # start SDS011 and MQ135

com = machine.UART(1, pins=('P20', 'P19'), baudrate = 9600) # UART communication to SDS011
sds011 = SDS011(com) # fine particle sensor (70 / 0.0 mA)

mq135 = MQ135('P17', duration = 50) # CO2 sensor (200 / 0.0 mA)

machine.sleep(t_wake * 1000) # wait for ~30 seconds

values['co2'] = mq135.get_corrected_ppm(values['temp'], values['humi'])

t1 = time.ticks_ms()
while (not sds011.read() and time.ticks_ms() - t1 < 5000): # try to get a response from SDS011 within 5 seconds
time.sleep_ms(10)

values['pm25'] = sds011.pm25
values['pm10'] = sds011.pm10
regulator.value(0) # disable voltage regulator
regulator.hold(True) # hold pin low during deepsleep

return values
1 change: 1 addition & 0 deletions software/counter.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0
Loading

0 comments on commit 081984d

Please sign in to comment.