MicroPython Skripte der unterschiedlichen Sensoren

Phyphox-Bibliothek

Zuerst muss man den PhyphoxBLE-Ordner, in der die Phyphox-Bibliothek gespeichert ist, herunterladen: https://github.com/phyphox/phyphox-micropython/tree/main/phyphoxBLE

Diesen Ordner muss man dann auf den ESP laden

Um Sensoren auszulesen und diese in Phyphox anzeigen zu lassen muss man nun die Bibliothek des gewünschten Sensors herunter- und auf den Mikrocontroller daraufladen

Kraftsensor

Kraftsensor-Bibliothek

from machine import Pin, enable_irq, disable_irq, idle

class HX711:
    def __init__(self, dout, pd_sck, gain=128):

        self.pSCK = Pin(pd_sck , mode=Pin.OUT)
        self.pOUT = Pin(dout, mode=Pin.IN, pull=Pin.PULL_DOWN)
        self.pSCK.value(False)

        self.GAIN = 0
        self.OFFSET = 0
        self.SCALE = 1
        
        self.time_constant = 0.1
        self.filtered = 0

        self.set_gain(gain);

    def set_gain(self, gain):
        if gain is 128:
            self.GAIN = 1
        elif gain is 64:
            self.GAIN = 3
        elif gain is 32:
            self.GAIN = 2

        self.read()
        self.filtered = self.read()
        print('Gain & initial value set')
    
    def is_ready(self):
        return self.pOUT() == 0

    def read(self):
        # wait for the device being ready
        while self.pOUT() == 1:
            idle()

        # shift in data, and gain & channel info
        result = 0
        for j in range(24 + self.GAIN):
            state = disable_irq()
            self.pSCK(True)
            self.pSCK(False)
            enable_irq(state)
            result = (result << 1) | self.pOUT()

        # shift back the extra bits
        result >>= self.GAIN

        # check sign
        if result > 0x7fffff:
            resualt -= 0x1000000

        return result

    def read_average(self, times=3):
        sum = 0
        for i in range(times):
            sum += self.read()
        return sum / times

    def read_lowpass(self):
        self.filtered += self.time_constant * (self.read() - self.filtered)
        return self.filtered

    def get_value(self, times=3):
        return self.read_average(times) - self.OFFSET

    def get_units(self, times=3):
        return self.get_value(times) / self.SCALE

    def tare(self, times=15):
        sum = self.read_average(times)
        self.set_offset(sum)

    def set_scale(self, scale):
        self.SCALE = scale

    def set_offset(self, offset):
        self.OFFSET = offset

    def set_time_constant(self, time_constant = None):
        if time_constant is None:
            return self.time_constant
        elif 0 < time_constant < 1.0:
            self.time_constant = time_constant

    def power_down(self):
        self.pSCK.value(False)
        self.pSCK.value(True)

    def power_up(self):
        self.pSCK.value(False)

Code für den Kraftsensor

from phyphoxBLE import PhyphoxBLE, Experiment
from machine import Pin, I2C, ADC
import machine
import time
from hx711 import HX711

dout_pin = 22
sck_pin = 23

hx = HX711(dout_pin, sck_pin)
hx.set_scale(1024)
hx.tare()

lastTimestamp = 0
blinkInterval = 1000
p = PhyphoxBLE()
p.debug = False

def main():
global lastTimestamp
p.start("Kraftsensor")
p._write_callback = receivedData
plot = Experiment()
plot.setTitle("Gewichts-Messung")
plot.setCategory("Micropython Experiments")
plot.setDescription("Der Microcontroller misst das Gewicht der Last")

firstView = Experiment.View()
firstView.setLabel("Gewichts-Messung")
secondView = Experiment.View()
secondView.setLabel("Einstellungen")

abstand = Experiment.Separator()
abstand.setHeight(2.0)

kraftgraph = Experiment.Graph()
kraftgraph.setLabel("Gewichtsmessung")
kraftgraph.setUnitX("s")
kraftgraph.setUnitY("g")
kraftgraph.setLabelX("Zeit")
kraftgraph.setLabelY("Gewicht")
kraftgraph.setXPrecision(1)
kraftgraph.setYPrecision(2)
kraftgraph.setChannel(0,1)

myValue1 = Experiment.Value()
myValue1.setLabel("Gewicht")
myValue1.setPrecision(2)
myValue1.setUnit("g")
myValue1.setColor("FFFFFF")
myValue1.setChannel(1)
myValue1.setXMLAttribute("size=\"2\"")

mySet = Experiment.ExportSet()
mySet.setLabel("Gewichts-Messungen")

myData1 = Experiment.ExportData() 
myData1.setLabel("Zeit")
myData1.setDatachannel(0)

myData2 = Experiment.ExportData() 
myData2.setLabel("Gewicht g")
myData2.setDatachannel(1)

Interval = Experiment.Edit() 
Interval.setLabel("Gewünschter Interval:")
Interval.setUnit("s")
Interval.setSigned(False)
Interval.setDecimal(False)
Interval.setChannel(1)
Interval.setXMLAttribute("min=\"1\"")

myValue2 = Experiment.Value()
myValue2.setLabel("Aktueller Interval:")
myValue2.setPrecision(0)
myValue2.setUnit("s")
myValue2.setColor("FFFFFF")
myValue2.setChannel(2)
myValue2.setXMLAttribute("size=\"2\"")

firstView.addElement(abstand)
firstView.addElement(kraftgraph)
firstView.addElement(abstand)
firstView.addElement(myValue1)
secondView.addElement(abstand)
secondView.addElement(myValue2)
secondView.addElement(Interval)
plot.addView(firstView)
plot.addView(secondView)
mySet.addElement(myData1)
mySet.addElement(myData2)
plot.addExportSet(mySet)
p.addExperiment(plot)

while True:
    if time.ticks_ms()-lastTimestamp > blinkInterval:
        lastTimestamp = time.ticks_ms()
        try:
            weight = hx.get_units()
        except ValueError:
            weight=0
        print(weight)
        machine.idle()
        p.write(weight,blinkInterval/1000)

def receivedData():
global blinkInterval
receivedInterval = p.read()
if receivedInterval > 0 and receivedInterval != blinkInterval:
print("New Interval: ", receivedInterval)
blinkInterval = receivedInterval*1000

if name == "main":
main()

Temperatursensor

Bibliothek vom Temperatursensor

OneWire

# DS18x20 temperature sensor driver for MicroPython.
# MIT license; Copyright (c) 2016 Damien P. George

from micropython import const
from machine import Pin

CMD_CONVERT = const(0x44)
CMD_RDSCRATCH = const(0xbe)
CMD_WRSCRATCH = const(0x4e)
CMD_RDPOWER = const(0xb4)
PULLUP_ON = const(1)
PULLUP_OFF = const(0)

class DS18X20:
    def __init__(self, onewire):
        self.ow = onewire
        self.buf = bytearray(9)
        self.config = bytearray(3)
        self.power = 1 # strong power supply by default
        self.powerpin = None

    def powermode(self, powerpin=None):
        if self.powerpin is not None: # deassert strong pull-up
            self.powerpin(PULLUP_OFF)
        self.ow.writebyte_byte(self.ow.CMD_SKIPROM)
        self.ow.writebyte(CMD_RDPOWER)
        self.power = self.ow.readbit()
        if powerpin is not None:
            assert type(powerpin) is Pin, "Parameter must be a Pin object"
            self.powerpin = powerpin
            self.powerpin.init(mode=Pin.OUT, value=0)
        return self.power

    def scan(self):
        if self.powerpin is not None: # deassert strong pull-up
            self.powerpin(PULLUP_OFF)
        return [rom for rom in self.ow.scan() if rom[0] in (0x10, 0x22, 0x28)]

    def convert_temp(self, rom=None):
        if self.powerpin is not None: # deassert strong pull-up
            self.powerpin(PULLUP_OFF)
        self.ow.reset()
        if rom is None:
            self.ow.writebyte(self.ow.CMD_SKIPROM)
        else:
            self.ow.select_rom(rom)
        self.ow.writebyte(CMD_CONVERT, self.powerpin)

    def read_scratch(self, rom):
        if self.powerpin is not None: # deassert strong pull-up
            self.powerpin(PULLUP_OFF)
        self.ow.reset()
        self.ow.select_rom(rom)
        self.ow.writebyte(CMD_RDSCRATCH)
        self.ow.readinto(self.buf)
        assert self.ow.crc8(self.buf) == 0, 'CRC error'
        return self.buf

    def write_scratch(self, rom, buf):
        if self.powerpin is not None: # deassert strong pull-up
            self.powerpin(PULLUP_OFF)
        self.ow.reset()
        self.ow.select_rom(rom)
        self.ow.writebyte(CMD_WRSCRATCH)
        self.ow.write(buf)

    def read_temp(self, rom):
        try:
            buf = self.read_scratch(rom)
            if rom[0] == 0x10:
                if buf[1]:
                    t = buf[0] >> 1 | 0x80
                    t = -((~t + 1) & 0xff)
                else:
                    t = buf[0] >> 1
                return t - 0.25 + (buf[7] - buf[6]) / buf[7]
            elif rom[0] in (0x22, 0x28):
                t = buf[1] << 8 | buf[0]
                if t & 0x8000: # sign bit set
                    t = -((t ^ 0xffff) + 1)
                return t / 16
            else:
                return None
        except AssertionError:
            return None

    def resolution(self, rom, bits=None):
        if bits is not None and 9 <= bits <= 12:
            self.config[2] = ((bits - 9) << 5) | 0x1f
            self.write_scratch(rom, self.config)
            return bits
        else:
            data = self.read_scratch(rom)
            return ((data[4] >> 5) & 0x03) + 9

    def fahrenheit(self, celsius):
        return celsius * 1.8 + 32 if celsius is not None else None

    def kelvin(self, celsius):
        return celsius + 273.15 if celsius is not None else None

DS18X20-Bibliothek

# DS18x20 temperature sensor driver for MicroPython.
# MIT license; Copyright (c) 2016 Damien P. George

from micropython import const
from machine import Pin

CMD_CONVERT = const(0x44)
CMD_RDSCRATCH = const(0xbe)
CMD_WRSCRATCH = const(0x4e)
CMD_RDPOWER = const(0xb4)
PULLUP_ON = const(1)
PULLUP_OFF = const(0)

class DS18X20:
    def __init__(self, onewire):
        self.ow = onewire
        self.buf = bytearray(9)
        self.config = bytearray(3)
        self.power = 1 # strong power supply by default
        self.powerpin = None

    def powermode(self, powerpin=None):
        if self.powerpin is not None: # deassert strong pull-up
            self.powerpin(PULLUP_OFF)
        self.ow.writebyte_byte(self.ow.CMD_SKIPROM)
        self.ow.writebyte(CMD_RDPOWER)
        self.power = self.ow.readbit()
        if powerpin is not None:
            assert type(powerpin) is Pin, "Parameter must be a Pin object"
            self.powerpin = powerpin
            self.powerpin.init(mode=Pin.OUT, value=0)
        return self.power

    def scan(self):
        if self.powerpin is not None: # deassert strong pull-up
            self.powerpin(PULLUP_OFF)
        return [rom for rom in self.ow.scan() if rom[0] in (0x10, 0x22, 0x28)]

    def convert_temp(self, rom=None):
        if self.powerpin is not None: # deassert strong pull-up
            self.powerpin(PULLUP_OFF)
        self.ow.reset()
        if rom is None:
            self.ow.writebyte(self.ow.CMD_SKIPROM)
        else:
            self.ow.select_rom(rom)
        self.ow.writebyte(CMD_CONVERT, self.powerpin)

    def read_scratch(self, rom):
        if self.powerpin is not None: # deassert strong pull-up
            self.powerpin(PULLUP_OFF)
        self.ow.reset()
        self.ow.select_rom(rom)
        self.ow.writebyte(CMD_RDSCRATCH)
        self.ow.readinto(self.buf)
        assert self.ow.crc8(self.buf) == 0, 'CRC error'
        return self.buf

    def write_scratch(self, rom, buf):
        if self.powerpin is not None: # deassert strong pull-up
            self.powerpin(PULLUP_OFF)
        self.ow.reset()
        self.ow.select_rom(rom)
        self.ow.writebyte(CMD_WRSCRATCH)
        self.ow.write(buf)

    def read_temp(self, rom):
        try:
            buf = self.read_scratch(rom)
            if rom[0] == 0x10:
                if buf[1]:
                    t = buf[0] >> 1 | 0x80
                    t = -((~t + 1) & 0xff)
                else:
                    t = buf[0] >> 1
                return t - 0.25 + (buf[7] - buf[6]) / buf[7]
            elif rom[0] in (0x22, 0x28):
                t = buf[1] << 8 | buf[0]
                if t & 0x8000: # sign bit set
                    t = -((t ^ 0xffff) + 1)
                return t / 16
            else:
                return None
        except AssertionError:
            return None

    def resolution(self, rom, bits=None):
        if bits is not None and 9 <= bits <= 12:
            self.config[2] = ((bits - 9) << 5) | 0x1f
            self.write_scratch(rom, self.config)
            return bits
        else:
            data = self.read_scratch(rom)
            return ((data[4] >> 5) & 0x03) + 9

    def fahrenheit(self, celsius):
        return celsius * 1.8 + 32 if celsius is not None else None

    def kelvin(self, celsius):
        return celsius + 273.15 if celsius is not None else None

Code für den Temperatursensor

from phyphoxBLE import PhyphoxBLE, Experiment
from machine import Pin
from onewire import OneWire
from ds18x20 import DS18X20
import time

pin = Pin(0)
tempSensor = DS18X20(OneWire(pin))
lastTimestamp = 0
blinkInterval = 1000
sensors = tempSensor.scan()
p = PhyphoxBLE()
p.debug = False

def main():
    global lastTimestamp
    p.start("Temperatursensor")
    p._write_callback = receivedData
    plot = Experiment()
    plot.setTitle("Temperaturmessungen")
    plot.setCategory("Micropython Experiments")
    plot.setDescription("Der Microcontroller misst die Temperatur")

    firstView = Experiment.View()
    firstView.setLabel("Temperaturmessung")
    secondView = Experiment.View()
    secondView.setLabel("Einstellungen")
    
    abstand = Experiment.Separator()
    abstand.setHeight(2.0)
    
    #Erste View
    
    firstGraph = Experiment.Graph()
    firstGraph.setLabel("Temperaturmessungen")
    firstGraph.setUnitY("°C")
    firstGraph.setLabelY("Temperatur")
    firstGraph.setXPrecision(1)
    firstGraph.setYPrecision(2)
    firstGraph.setChannel(0,1)
    
    secondGraph = Experiment.Graph()
    secondGraph.setUnitY("°C")
    secondGraph.setLabelY("Temperatur")
    secondGraph.setXPrecision(1)
    secondGraph.setYPrecision(2)
    secondGraph.setChannel(0,2)
    
    thirdGraph = Experiment.Graph()
    thirdGraph.setUnitY("°C")
    thirdGraph.setLabelY("Temperatur")
    thirdGraph.setUnitX("s")
    thirdGraph.setLabelX("Zeit")
    thirdGraph.setXPrecision(1)
    thirdGraph.setYPrecision(2)
    thirdGraph.setChannel(0,3)
    
    mySet = Experiment.ExportSet()
    mySet.setLabel("Temperaturmessungen")

    myData1 = Experiment.ExportData() 
    myData1.setLabel("Zeit")
    myData1.setDatachannel(0)

    myData2 = Experiment.ExportData() 
    myData2.setLabel("Temperatur Celsius")
    myData2.setDatachannel(1)
    
    myData3 = Experiment.ExportData() 
    myData3.setLabel("Temperatur Kelvin")
    myData3.setDatachannel(2)
    
    Interval = Experiment.Edit() 
    Interval.setLabel("Gewünschter Interval:")
    Interval.setUnit("s")
    Interval.setSigned(False)
    Interval.setDecimal(False)
    Interval.setChannel(1)
    Interval.setXMLAttribute("min=\"1\"")
    
    myValue = Experiment.Value()
    myValue.setLabel("Aktueller Interval:")
    myValue.setPrecision(0)
    myValue.setUnit("s")
    myValue.setColor("FFFFFF")
    myValue.setChannel(4)
    myValue.setXMLAttribute("size=\"2\"")
    
    firstView.addElement(abstand)
    firstView.addElement(firstGraph)
    firstView.addElement(secondGraph)
    firstView.addElement(thirdGraph)
    secondView.addElement(abstand)
    secondView.addElement(myValue)
    secondView.addElement(Interval)
    plot.addView(firstView)
    plot.addView(secondView)
    mySet.addElement(myData1)
    mySet.addElement(myData2)
    mySet.addElement(myData3)
    plot.addExportSet(mySet)
    p.addExperiment(plot)
    
    while True:
        
        if time.ticks_ms()-lastTimestamp > blinkInterval:
            lastTimestamp = time.ticks_ms()
            tempSensor.convert_temp()
            digits = 2
            for sensor in sensors:
                temp = tempSensor.read_temp(sensor)
                if temp is not None and type(temp) == float:
                    if sensor == sensors[0]:
                        tempCelsius1 = round(temp, digits)
                    elif sensor == sensors[1]:
                        tempCelsius2 = round(temp, digits)
                    elif sensor == sensors[2]:
                        tempCelsius3 = round(temp, digits)

            p.write(tempCelsius1,tempCelsius2,tempCelsius3,blinkInterval/1000)

def receivedData():
    global blinkInterval
    receivedInterval = p.read()
    if receivedInterval > 0 and receivedInterval != blinkInterval:
        print("New Interval: ", receivedInterval)
        blinkInterval = receivedInterval*1000

if __name__ == "__main__":
    main()

CO2-Sensor

SCD30-Bibliothek

from machine import I2C
import utime
import struct

class SCD30:

    class NotFoundException(Exception):
        pass

    class CRCException(Exception):
        pass

    START_CONT_MEASURE = 0x0010
    STOP_CONT_MEASURE = 0x0104
    SET_MEASURE_INTERVAL = 0x4600
    GET_STATUS_READY = 0x0202
    READ_MEASUREMENT = 0x0300
    SET_ASC = 0x5306
    SET_FRC = 0x5204
    SET_TEMP_OFFSET = 0x5403
    SET_ALT_COMP = 0x5102
    GET_FIRMWARE_VER = 0xd100
    SOFT_RESET = 0xd304

    CLOCK_TIME_US = 10

    # Generated using
    # crc_table = []
    # for crc in range(256):
    #     for crc_bit in range(8):
    #         if crc & 0x80:
    #             crc = (crc << 1) ^ CRC8_POLYNOMIAL;
    #         else:
    #             crc = (crc << 1);
    #         crc = crc%256
    #     crc_table.append(crc)

    CRC_TABLE = [
        0, 49, 98, 83, 196, 245, 166, 151, 185, 136, 219, 234, 125, 76, 31, 46,
        67, 114, 33, 16, 135, 182, 229, 212, 250, 203, 152, 169, 62, 15, 92, 109,
        134, 183, 228, 213, 66, 115, 32, 17, 63, 14, 93, 108, 251, 202, 153, 168,
        197, 244, 167, 150, 1, 48, 99, 82, 124, 77, 30, 47, 184, 137, 218, 235,
        61, 12, 95, 110, 249, 200, 155, 170, 132, 181, 230, 215, 64, 113, 34, 19,
        126, 79, 28, 45, 186, 139, 216, 233, 199, 246, 165, 148, 3, 50, 97, 80,
        187, 138, 217, 232, 127, 78, 29, 44, 2, 51, 96, 81, 198, 247, 164, 149,
        248, 201, 154, 171, 60, 13, 94, 111, 65, 112, 35, 18, 133, 180, 231, 214,
        122, 75, 24, 41, 190, 143, 220, 237, 195, 242, 161, 144, 7, 54, 101, 84,
        57, 8, 91, 106, 253, 204, 159, 174, 128, 177, 226, 211, 68, 117, 38, 23,
        252, 205, 158, 175, 56, 9, 90, 107, 69, 116, 39, 22, 129, 176, 227, 210,
        191, 142, 221, 236, 123, 74, 25, 40, 6, 55, 100, 85, 194, 243, 160, 145,
        71, 118, 37, 20, 131, 178, 225, 208, 254, 207, 156, 173, 58, 11, 88, 105,
        4, 53, 102, 87, 192, 241, 162, 147, 189, 140, 223, 238, 121, 72, 27, 42,
        193, 240, 163, 146, 5, 52, 103, 86, 120, 73, 26, 43, 188, 141, 222, 239,
        130, 179, 224, 209, 70, 119, 36, 21, 59, 10, 89, 104, 255, 206, 157, 172
        ]

    def __init__(self, i2c, addr, pause=1000):
        self.i2c = i2c
        self.pause = pause
        self.addr = addr
        if not addr in i2c.scan():
            raise self.NotFoundException

    def start_continous_measurement(self, ambient_pressure=0):
        bint = struct.pack('>H', ambient_pressure)
        crc = self.__crc(bint[0], bint[1])
        data = bint + bytes([crc])
        self.i2c.writeto_mem(self.addr, self.START_CONT_MEASURE, data, addrsize=16)

    def stop_continous_measurement(self):
        self.__write_command(self.STOP_CONT_MEASURE)

    def soft_reset(self):
        self.__write_command(self.SOFT_RESET)

    def get_firmware_version(self):
        ver = self.__read_bytes(self.GET_FIRMWARE_VER, 3)
        self.__check_crc(ver)
        return struct.unpack('BB', ver)

    def read_measurement(self):
        measurement = self.__read_bytes(self.READ_MEASUREMENT, 18)
        for i in range(0, len(measurement), 3):
            self.__check_crc(measurement[i:i+3])

        value = measurement[0:]
        co2 = struct.unpack('>f', value[0:2] + value[3:5])[0]
        value = measurement[6:]
        temperature = struct.unpack('>f', value[0:2] + value[3:5])[0]
        value = measurement[12:]
        relh = struct.unpack('>f', value[0:2] + value[3:5])[0]
        return (co2, temperature, relh)

    def get_status_ready(self):
        ready = self.__read_bytes(self.GET_STATUS_READY, 3)
        self.__check_crc(ready)
        return struct.unpack('>H', ready)[0]

    def get_measurement_interval(self):
        bint = self.__read_bytes(self.SET_MEASURE_INTERVAL, 3)
        self.__check_crc(bint)
        return struct.unpack('>H', bint)[0]

    def set_measurement_interval(self, interval):
        bint = struct.pack('>H', interval)
        crc = self.__crc(bint[0], bint[1])
        data = bint + bytes([crc])
        self.i2c.writeto_mem(self.addr, self.SET_MEASURE_INTERVAL, data, addrsize=16)

    def get_automatic_recalibration(self):
        bint = self.__read_bytes(self.SET_ASC, 3)
        self.__check_crc(bint)
        return struct.unpack('>H', bint)[0] == 1

    def set_automatic_recalibration(self, enable):
        bint = struct.pack('>H', 1 if enable else 0)
        crc = self.__crc(bint[0], bint[1])
        data = bint + bytes([crc])
        self.i2c.writeto_mem(self.addr, self.SET_FRC, data, addrsize=16)

    def get_forced_recalibration(self):
        bint = self.__read_bytes(self.SET_FRC, 3)
        self.__check_crc(bint)
        return struct.unpack('>H', bint)[0]

    def set_forced_recalibration(self, co2ppm):
        bint = struct.pack('>H', co2ppm)
        crc = self.__crc(bint[0], bint[1])
        data = bint + bytes([crc])
        self.i2c.writeto_mem(self.addr, self.SET_FRC, data, addrsize=16)

    def get_temperature_offset(self):
        bint = self.__read_bytes(self.SET_TEMP_OFFSET, 3)
        self.__check_crc(bint)
        return struct.unpack('>H', bint)[0] / 100.0

    def set_temperature_offset(self, offset):
        bint = struct.pack('>H', int(offset * 100))
        crc = self.__crc(bint[0], bint[1])
        data = bint + bytes([crc])
        self.i2c.writeto_mem(self.addr, self.SET_TEMP_OFFSET, data, addrsize=16)

    def get_altitude_comp(self):
        bint = self.__read_bytes(self.SET_ALT_COMP, 3)
        self.__check_crc(bint)
        return struct.unpack('>H', bint)[0]

    def set_altitude_comp(self, altitude):
        bint = struct.pack('>H', altitude)
        crc = self.__crc(bint[0], bint[1])
        data = bint + bytes([crc])
        self.i2c.writeto_mem(self.addr, self.SET_ALT_COMP, data, addrsize=16)

    def __write_command(self, cmd):
        bcmd = struct.pack('>H', cmd)
        self.i2c.writeto(self.addr, bcmd)

    def __read_bytes(self, cmd, count):
        self.__write_command(cmd)
        utime.sleep_us(self.pause)
        return self.i2c.readfrom(self.addr, count)

    def __check_crc(self, arr):
        assert (len(arr) == 3)
        if self.__crc(arr[0], arr[1]) != arr[2]:
            raise self.CRCException

    def __crc(self, msb, lsb):
        crc = 0xff
        crc ^= msb
        crc = self.CRC_TABLE[crc]
        if lsb is not None:
            crc ^= lsb
            crc = self.CRC_TABLE[crc]
        return crc

SCD30-Code

from phyphoxBLE import PhyphoxBLE, Experiment
from machine import Pin, SoftI2C
from scd30 import SCD30
from machine import Pin, I2C
from time import sleep_ms

i2c = SoftI2C(sda=Pin(21), scl=Pin(22))
readyPin = Pin(23, Pin.IN, Pin.PULL_UP)
devices = i2c.scan()

for device in devices:
    if device == 0x61:
        scd30_address = device
        break

scd30_sensor = SCD30(i2c, scd30_address)
scd30_sensor.start_continous_measurement()

blinkInterval = 300000
lastTimestamp = 0
p = PhyphoxBLE()
p.debug = False

def main():
    global lastTimestamp
    p.start("CO2-Sensor")
    p._write_callback = receivedData
    plot = Experiment()
    plot.setTitle("CO2-Messung")
    plot.setCategory("Micropython Experiments")
    plot.setDescription("Der Microcontroller misst den CO2-Gehalt in der Luft")

    firstView = Experiment.View()
    firstView.setLabel("CO2-Messung")
    secondView = Experiment.View()
    secondView.setLabel("Einstellungen")
    
    abstand = Experiment.Separator()
    abstand.setHeight(2.0)
    
    #Erste View
    
    firstGraph = Experiment.Graph()
    firstGraph.setLabel("CO2-Messung")
    firstGraph.setUnitY("ppm")
    firstGraph.setLabelY("Konzentration")
    firstGraph.setXPrecision(1)
    firstGraph.setYPrecision(2)
    firstGraph.setChannel(0,1)
    
    secondGraph = Experiment.Graph()
    secondGraph.setUnitY("°C")
    secondGraph.setLabelY("Temperatur")
    secondGraph.setXPrecision(1)
    secondGraph.setYPrecision(2)
    secondGraph.setChannel(0,2)
    
    thirdGraph = Experiment.Graph()
    thirdGraph.setUnitY("%")
    thirdGraph.setLabelY("Luftfeuchtigkeit")
    thirdGraph.setUnitX("s")
    thirdGraph.setLabelX("Zeit")
    thirdGraph.setXPrecision(1)
    thirdGraph.setYPrecision(2)
    thirdGraph.setChannel(0,3)
    
    mySet = Experiment.ExportSet()
    mySet.setLabel("CO2-Messungen")

    myData1 = Experiment.ExportData() 
    myData1.setLabel("Zeit")
    myData1.setDatachannel(0)

    myData2 = Experiment.ExportData() 
    myData2.setLabel("CO2-Konzentration")
    myData2.setDatachannel(1)
    
    myData3 = Experiment.ExportData() 
    myData3.setLabel("Temperatur")
    myData3.setDatachannel(2)
    
    myData4 = Experiment.ExportData() 
    myData4.setLabel("Luftfeuchtigkeit")
    myData4.setDatachannel(3)
    
    Interval = Experiment.Edit() 
    Interval.setLabel("Gewünschter Interval:")
    Interval.setUnit("s")
    Interval.setSigned(False)
    Interval.setDecimal(False)
    Interval.setChannel(1)
    Interval.setXMLAttribute("min=\"1\"")
    
    myValue = Experiment.Value()
    myValue.setLabel("Aktueller Interval:")
    myValue.setPrecision(0)
    myValue.setUnit("s")
    myValue.setColor("FFFFFF")
    myValue.setChannel(4)
    myValue.setXMLAttribute("size=\"2\"")
    
    firstView.addElement(abstand)
    firstView.addElement(firstGraph)
    firstView.addElement(secondGraph)
    firstView.addElement(thirdGraph)
    secondView.addElement(abstand)
    secondView.addElement(myValue)
    secondView.addElement(Interval)
    plot.addView(firstView)
    plot.addView(secondView)
    mySet.addElement(myData1)
    mySet.addElement(myData2)
    mySet.addElement(myData3)
    mySet.addElement(myData4)
    plot.addExportSet(mySet)
    p.addExperiment(plot)
    
    while True:
        while readyPin.value() != 1:
            sleep_ms(200)
        # Lese die Sensordaten
        co2, temperature, humidity = scd30_sensor.read_measurement()

        # Runde die Werte auf zwei Nachkommastellen
        co2 = round(co2, 2)
        temperature = round(temperature, 2)
        humidity = round(humidity, 2)
        
        
        # Gib die Werte aus
        print("******************************")
        print("CO2: {} ppm".format(co2))
        print("Temperatur: {} °C".format(temperature))
        print("Luftfeuchtigkeit: {} %".format(humidity))
        
        p.write(co2,temperature,humidity,blinkInterval/1000)
            
def receivedData():
    global blinkInterval
    receivedInterval = p.read()
    if receivedInterval > 0 and receivedInterval != blinkInterval:
        print("New Interval: ", receivedInterval)
        blinkInterval = receivedInterval*1000
        
if __name__ == "__main__":
    main()

Temperatursensor

In diesem Experiment wird die Wärmetransmission unterschiedlicher Oberflächen untersucht.

Drei gleichgrosse Flaschen werden hierzu mit heissem Wasser randvoll befüllt. Die Wassertemperatur im Flascheninnern soll mittels Temperaturfühler über die Zeit gemessen und aufgezeichnet werden. Die Flaschen haben einzig unterschiedliche Oberflächenbeschichtungen (schwarz lackiert, weiss lackiert und alu-gebürstet). Die Fragestellung lautet, welchen messbaren Effekt die Flaschenbeschichtung auf die Abkühlung hat.

Die Messdaten werden über den Microcontroller ESP32 via Bluetooth-Schnittstelle an die PhyPhox Smartphone App gesendet und dort in Echtzeit grafisch dargestellt. Zusätzlich wurden die Messwerte aufgezeichnet und nach Abschluss des Experiments, in einer CSV-Datei zur finalen Auswertung auf den PC übertragen.

Versuchsaufbau / Experiment

Abb. 1: Versuchsaufbau, ESP32 mit Breadboard-Schaltung, Temperaturfühlern und Wasserflaschen

Schaltung

Abb. 2: Schaltbild der Temperatursensoren

Jeder Temperatursensor besitzt 3 Pins, siehe Abbildung 2.

Der Pin 1 (GND) wird jeweils mit der «Ground»-Schiene verbunden, sodass dort 0 Volt anliegen.

Der Pin 2 wird mit einem ESP32-IO Pin verbunden, um Daten übermitteln zu können. Da der Pin 2 eine Hilfsspannung benötigt, wird von der 3.3 Volt Schiene mittels eines Widerstands Pin 2 und Pin 3 verbunden.

Der Pin 3 wird mit 3.3 Volt, der «VDD»-Schiene verbunden.

Die Sensoren können parallel verbunden werden, sodass nur ein Datenkabel zum ESP32 benötigt wird, was auch eine parasitische Schaltung genannt wird. Die Sensoren haben jeweils eine eigene Buskennung und können über ihre ID vom Microcontroller separat nacheinander ausgelesen werden.

Bibliotheken und Module auf dem ESP32

Abb. 3: Bibliotheken und Dateien auf dem ESP32 (Thonny screenshot)
  • phyphoxBLE (Ordner, für Smartphone App)
  • ds18x20.py (Modul des Temperatursensors)
  • onewire.py (Modul für Bus-Schaltung)
  • boot.py (hier nicht verwendet)
  • temp_measurement v0.9.py (Quellcode PhyPhox Experiment)

Resultate – PhyPhox App Experiment «T-Plotter v0.9»

Abb. 4: Screenshot der PhyPhox App nach einer Temperaturmessung von ca. 2 Stunden.

Auswertung des Experiments

Abb. 5: Temperaturverlauf – Gesamtmessung

In Abbildung 5 wird die Wassertemperatur in den drei Wasserflaschen über die gesamte Messdauer von 6803 Messpunkten dargestellt. Das entspricht einem Messzeitraum von knapp 2 Stunden.

Nach Ablauf des Experiments ergibt sich eine deutlich höhere Wassertemperatur in der aluminium-gebürsteten Wasserflasche, welche im Vergleich zu den beiden anderen Wasserflaschen mit 43.5°C um 8.5°C höher liegt.

Die schwarze und die weisse Wasserflasche weisen einen sehr ähnlichen Temperaturverlauf auf.

Abb. 6: Temperaturverlauf – erste 80 Messwerte

Bei genauerer Betrachtung fällt auf, dass die schwarze Wasserflasche schneller abkühlt als die weisse. In Abbildung 6 beträgt die Wassertemperatur zu Beginn der Messung ca. 88°C und damit 1°C mehr als die weisse Wasserflasche.

Temperaturdifferenz zwischen T2 (schwarz) und T3 (weiss)

Abbildung 7 zeigt die Temperaturdifferenz zwischen der schwarzen und der weissen Wasserflasche. Die Temperaturdifferenz wird 0dK nach knapp 1000 Messungen, also ca. 16 Minuten.

Die schwarze Wasserflasche erreicht nach ca. einer Stunde Messdauer mit knapp -1dK Differenz zur weissen Flasche ein relatives Minimum bei 50°C. Mit fortschreitender Messdauer wird die Temperaturdifferenz zwischen weisser und schwarzer Wasserflasche kleiner.

Damit kann hinreichend belegt werden, dass eine schwarze Oberflächenbeschichtung zu einer stärkeren Wärmeabgabe an die Umgebung führt als eine weisse Oberflächenbeschichtung oder gar eine alu-gebürstete Aluminiumoberfläche.

Versuchsaufbau / Experiment

Anhang: Source Code

Der nachfolgende python Quelltext enthält das PhyPhox-Experiment T-Plotter v0.9 und stammt aus der Datei: „temp_measurement v0.9.py“

from phyphoxBLE import PhyphoxBLE, Experiment
import time
import machine, onewire, ds18x20		#OV: from T-Sensor

# Variablen- und Funktionsdefinition
p = PhyphoxBLE()
editValue = 123.0
firstCall = True

ds_pin = machine.Pin(22)
ds_sensor = ds18x20.DS18X20(onewire.OneWire(ds_pin))
roms = ds_sensor.scan()

# -------- main() Function --------------------------------------
def main():
    # Name max length is 26 characters
    p.start("ESP32_PhyPhox_Messung")
    p._write_callback = receivedData
    
    #Define Experiment
    TempPlotter = Experiment()   
#generate experiment on ESP32 which plots temperatures over time
    TempPlotter.setTitle("T-Plotter v0.9")
    TempPlotter.setCategory("Micropython Experiments")
    TempPlotter.setDescription("In diesem Experiment werden Temperaturen mittels 3 Temperatursensoren gemessen. Eine grafische und numerische Auswertung dient der Vergleichbarkeit der Signale.")

    #Define Views
    firstView = Experiment.View()
    firstView.setLabel("3-Graphs")              #Create a "view"
    secondView = Experiment.View()
    secondView.setLabel("Values")          	    #Create a "view"
    thirdView = Experiment.View()
    thirdView.setLabel("T_amb")         	    #Create a "view"

    #Define Graphs
    firstGraph = Experiment.Graph()             
 #Create graph which will plot temperatures over time
    firstGraph.setLabel("Temperature 1")
    firstGraph.setUnitX("s")
    firstGraph.setUnitY("°C")
    firstGraph.setLabelX("time")
    firstGraph.setLabelY("T_1")
    firstGraph.setChannel(0, 1)

    secondGraph = Experiment.Graph()             
#Create graph which will plot temp_2 over time
    secondGraph.setLabel("Temperature 2")
    secondGraph.setUnitX("s")
    secondGraph.setUnitY("°C")
    secondGraph.setLabelX("time")
    secondGraph.setLabelY("T_2")
    secondGraph.setColor("2E728E")               #Sets Color of line
    secondGraph.setChannel(0, 2)

    thirdGraph = Experiment.Graph()              
#Create graph which will plot temperatures over time
    thirdGraph.setLabel("Temperature 3")
    thirdGraph.setUnitX("s")
    thirdGraph.setUnitY("°C")
    thirdGraph.setLabelX("time")
    thirdGraph.setLabelY("T_3")
    thirdGraph.setColor("2E727E")                #Sets Color of line
    thirdGraph.setChannel(0, 3)


# ---------------------------------------------------------------
# Definition der Elemente (Objekte) fuer die zweite Seite (View)
# (wieder eingefuegt aus dem Template)

    #Info
    myInfo = Experiment.InfoField()             
#Creates an info-box.
    myInfo.setInfo("Hier stehen momentante Temperaturwerte. Die Umgebungstemperatur kann manuell eingegeben werden")
    myInfo.setColor("FFFFFF")                   
#Sets font color. Uses a 6 digit hexadecimal value in "quotation marks".
    myInfo.setXMLAttribute("size=\"1.2\"")

    #Separator
    mySeparator = Experiment.Separator()        
#Creates a line to separate elements.
    mySeparator.setHeight(2.3)                  
#Sets height of the separator.
    mySeparator.setColor("404040")              
#Sets color of the separator. Uses a 6 digit hexadecimal value in "quotation marks".

    #Value1 (Temp1)
    myValue1 = Experiment.Value()  		#Creates a value-box.
    myValue1.setLabel("Temp_1")                  #Sets the label
    myValue1.setPrecision(1)                   
#The amount of digits shown after the decimal point.
    myValue1.setUnit("°C")                        
#The physical unit associated with the displayed value.
    myValue1.setColor("FFFFFF")                  
#Sets font color. Uses a 6 digit hexadecimal value in "quotation marks".
    myValue1.setChannel(1)
    myValue1.setXMLAttribute("size=\"3\"")

    #Value2 (T2)
    myValue2 = Experiment.Value()  		#Creates a value-box.
    myValue2.setLabel("Temp_2")                  #Sets the label
    myValue2.setPrecision(1)                     
#The amount of digits shown after the decimal point.
    myValue2.setUnit("°C")                        
#The physical unit associated with the displayed value.
    myValue2.setColor("FFFFFF")                  
#Sets font color. Uses a 6 digit hexadecimal value in "quotation marks".
    myValue2.setChannel(2)
    myValue2.setXMLAttribute("size=\"3\"")

    #Value3 (T3)
    myValue3 = Experiment.Value()  		#Creates a value-box.
    myValue3.setLabel("Temp_3")                  #Sets the label
    myValue3.setPrecision(1)                    
#The amount of digits shown after the decimal point.
    myValue3.setUnit("°C")                        
#The physical unit associated with the displayed value.
    myValue3.setColor("FFFFFF")                  
#Sets font color. Uses a 6 digit hexadecimal value in "quotation marks".
    myValue3.setChannel(3)
    myValue3.setXMLAttribute("size=\"3\"")

    #Value4 (Samples Counter, the integer i in the while loop below in the code)
    myValue4 = Experiment.Value()  		#Creates a value-box.
    myValue4.setLabel("n-te Lesung")                  #Sets the label
    myValue4.setPrecision(1)                     
#The amount of digits shown after the decimal point.
    myValue4.setUnit("")                        
#The physical unit associated with the displayed value.
    myValue4.setColor("FFFFFF")                  
#Sets font color. Uses a 6 digit hexadecimal value in "quotation marks".
    myValue4.setChannel(4)
    myValue4.setXMLAttribute("size=\"1\"")

    #Value5 (Hier ist nur der editValue Wert drin)
    myValue5 = Experiment.Value()  				
#Creates a value-box.
    myValue5.setLabel("Eingegeben")                  #Sets the label
    myValue5.setPrecision(1)                     
#The amount of digits shown after the decimal point.
    myValue5.setUnit("°C")                        
#The physical unit associated with the displayed value.
    myValue5.setColor("FFFFFF")                  
#Sets font color. Uses a 6 digit hexadecimal value in "quotation marks".
    myValue5.setChannel(5)
    myValue5.setXMLAttribute("size=\"1\"")

    #Edit
    myEdit = Experiment.Edit() 
    myEdit.setLabel("Umgebung")
    myEdit.setUnit("°C")
    myEdit.setSigned(False)
    myEdit.setDecimal(False)
    myEdit.setChannel(1)					
    myEdit.setXMLAttribute("max=\"100\"")

    #Export
    mySet = Experiment.ExportSet()              
#Provides exporting the data to excel etc.
    mySet.setLabel("meineDaten")

    myData1 = Experiment.ExportData() 
    myData1.setLabel("T1")
    myData1.setDatachannel(1)

    myData2 = Experiment.ExportData() 
    myData2.setLabel("T2")
    myData2.setDatachannel(2)

    myData3 = Experiment.ExportData() 			
    myData3.setLabel("T3")
    myData3.setDatachannel(3)




#  ---------------------------------------------------------------
# attach above defined elements (objects) to experiment
    firstView.addElement(firstGraph)            #attach graphs to view
    firstView.addElement(secondGraph)            
    firstView.addElement(thirdGraph)            

    secondView.addElement(myInfo)               #attach info to view
    secondView.addElement(mySeparator)       #attach separator to view
    secondView.addElement(myValue1)             #attach value to view
    secondView.addElement(myValue2)             #attach value to view
    secondView.addElement(myValue3)             #attach value to view

    thirdView.addElement(mySeparator)       #attach separator to view
    thirdView.addElement(myValue4)             #attach value to view
    thirdView.addElement(myValue5)             #attach value to view
    thirdView.addElement(myEdit)               
#attach editfield to view (Linked to value)

    TempPlotter.addView(firstView)          #attach view to experiment
    TempPlotter.addView(secondView)
    TempPlotter.addView(thirdView)

   
    p.addExperiment(TempPlotter)         #attach experiment to server

    
# --- Erste Schleife ---- Programm
# Code von DS18 Temperatursensor

    i = 0

    while True:
        ds_sensor.convert_temp()
        time.sleep_ms(900)
    
        i = i+1;
        t1 = ds_sensor.read_temp(roms[0])
        t2 = ds_sensor.read_temp(roms[1])
        t3 = ds_sensor.read_temp(roms[2])
        t_amb = 123;
        
        p.write(t1, t2, t3, i, 123)

        time.sleep_ms(100)




def receivedData():          # get data from PhyPhox app
    global editValue
    global firstCall
    if not firstCall:
        editValue = float(p.read())
    firstCall = False
    
if __name__ == "__main__":
    main()

PhyPhox

PhyPhox ist eine leistungsstarke App für Android und iOS, die es Ihnen ermöglicht, physikalische Experimente und Messungen auf Ihrem Smartphone oder Tablet durchzuführen. Die App wurde von der Universität Siegen entwickelt und ist für den Einsatz im Unterricht, Labor oder für Forschungszwecke geeignet. PhyPhox ist eine benutzerfreundliche Anwendung, die für alle Altersgruppen geeignet ist und Ihnen eine Fülle von Funktionen und Experimenten zur Verfügung stellt.

Mit PhyPhox können Sie eine Vielzahl von Experimenten durchführen, die das volle Potenzial Ihres Smartphones oder Tablets nutzen. Zum Beispiel können Sie mit der App den Beschleunigungssensor verwenden, um die Schwerkraft zu messen oder den Gyrosensor, um Drehbewegungen zu erfassen. Darüber hinaus können Sie auch den Magnetometer verwenden, um das Erdmagnetfeld zu messen, oder den Lichtsensor, um die Helligkeit zu messen.

PhyPhox ist auch eine großartige Ressource für den Unterricht, da die App spezielle Experimente für den Einsatz im Klassenzimmer bietet. Diese Experimente sind speziell für Schülerinnen und Schüler entwickelt worden, um ihnen dabei zu helfen, physikalische Konzepte zu verstehen und ihre Fähigkeiten im Umgang mit Experimenten und Messungen zu verbessern.

Mit PhyPhox können Sie Ihre Experimente aufzeichnen und die Ergebnisse in Echtzeit anzeigen lassen. Sie können die Ergebnisse auch in verschiedenen Formaten exportieren, um sie später zu analysieren oder zu präsentieren.

Insgesamt ist PhyPhox eine äußerst nützliche App für alle, die an physikalischen Experimenten und Messungen interessiert sind. Wenn Sie mehr über PhyPhox erfahren möchten oder Unterstützung bei der Verwendung der App benötigen, stehe ich gerne zur Verfügung.

PhyPhox BLE: Verwenden Sie phyphox mit Ihrem ESP32

PhyPhox ist eine Open-Source-App, die an der RWTH Aachen University entwickelt wurde und auf Android und iOS verfügbar ist. Ziel der App ist es, die Sensoren Ihres Smartphones für physikalische Experimente und Messungen zugänglich zu machen. Doch wussten Sie, dass Sie PhyPhox auch mit Ihrem ESP32-Mikrocontroller erweitern können?

Mit der PhyPhox BLE-Bibliothek können Sie den ESP32 nutzen, um Sensordaten an Ihr Smartphone zu senden oder umgekehrt, um Daten von Ihrem Smartphone zu Ihrem Mikrocontroller zu übertragen. Die Bibliothek erstellt eine Experiment-Konfiguration im flexiblen Dateiformat von PhyPhox, die Sie direkt auf Ihr Smartphone übertragen können.

Sie können diese Bibliothek verwenden, um eine Vielzahl von Experimenten durchzuführen, die das volle Potenzial Ihres ESP32 nutzen. Beispielsweise können Sie mit Hilfe der PhyPhox BLE-Bibliothek Sensordaten von Ihrem ESP32 an Ihr Smartphone senden, um sie in Echtzeit zu plotten und zu analysieren. Oder Sie können Sensordaten von Ihrem Smartphone an Ihren ESP32 senden, um sie in Ihrem eigenen MicroPython-Projekt zu verarbeiten.

Die PhyPhox BLE-Bibliothek ist eine großartige Möglichkeit, die Leistungsfähigkeit Ihres ESP32 voll auszuschöpfen und ihn in die Welt der physikalischen Experimente und Messungen einzuführen. Wenn Sie mehr über die Verwendung von PhyPhox mit Ihrem ESP32 erfahren möchten oder Unterstützung bei der Verwendung der PhyPhox BLE-Bibliothek benötigen, stehe ich Ihnen gerne zur Verfügung.

Hier folgen in Kürze ein paar Beispiel aus dem Unterricht.