Raspberry Pi

Weerstation en Euromillions melder

Dit project maakt gebruik van een Raspberry Pi 3B+, een Pi Sense HAT en een buzzer uit een Freenove Ultimate Starterkit.
De Sense HAT is een dochterbordje dat je boven op de Raspberry Pi plugt. Het heeft verschillende sensoren (gyroscoop, accelerometer, magnetometer, temperatuur, …) alsook een 8×8 RGB LED matrix. Voor dit project gebruiken we enkel de LED matrix.
De Sense HAT heeft een Python library om het display te sturen vanuit je eigen programma. Je kan tekst laten scrollen maar je kan ook elke LED apart aansturen in een RGB kleur.
Er zijn momenteel 2 toepassingen geïmplementeerd:
1) een weerstation dat doorlopend temperatuur, windsterkte, bewolkingsgraad en neerslaghoeveelheid voor de komende 2 dagen. Alle gegevens behalve de neerslaghoeveelheid worden van http://api.openweathermap.org gehaald en de neerslag van http://www.weather-forecast.com.
2) Euromillions melder. Op dinsdag en vrijdag avond vanaf 22 uur kijkt de software om de 5 minuten na of de resultaten voor België op de site https://www.euro-millions.com/results aanwezig zijn. Bij aanwezigheid volgen er 5 bieptonen via de buzzer en toont de display of er iets gewonnen is. Er wordt getoond hoeveel cijfers en sterren correct zijn en het gewonnen bedrag. Maar meestal komt er “Geen winst” op het display ;-(.
Hieronder vind je een foto van de Raspberry Pi met Sense HAT en de weertoepassing. Daarnaast is er wat meer uitleg over de weerfuncties die getoond worden. Er is ook een filmpje over de Euromillions toepassing die toont dat er gewonnen is.
De Python code voor deze toepassingen staat onderaan op deze pagina.

RPi
Functies weerstation display

Weerstation display:
Rechts: 1 tot 4 rode LEDs voor de temperatuur. 1 LED is 0 tot 10 graden. 2 LEDs is 10 tot 20 graden. Enz. Hetzelfde geldt voor negatieve temperaturen maar dan zijn het blauwe LEDs. De eerste LED flasht om de 5 seconden als heartbeat voor de toepassing.
Midden: Temperatuur op te tellen bij de rechtse rode of blauwe LEDs. In het voorbeeld branden er 3 rode LEDs wat 20 graden betekent en we tellen er 4 graden bij wat dus 24 graden geeft. Als de LED in de linker benedenhoek brandt (wat hier niet het geval is), komt er nog een halve graad bij.
Rechter benedenhoek: rood = temperatuur stijgt, blauw =  daalt.
Midden beneden: Windsterkte 1 tot 5 groene LEDs (vb matige wind).
Midden boven: Bewolkingsgraad 1 tot 5 purperen LEDs (vb zwaar bewolkt).
Links: Neerslag verwachting 1 LED per 8 uur. 6 LEDs is dus voor de komende 48 uur (2 dagen). Groene LED is geen neerslag, gele LED is beetje neerslag en rood is veel neerslag.

Euro-millions melder:
Om de aandacht te trekken als de resultaten van de Euro-millions trekking binnen zijn, gebruiken we een buzzer die een aantal malen piept zodat we naar de display kijken om onze potentiële winst te bekijken.

De Raspberry Pi heeft een aantal GPIO poorten die we via een Python library (RPi.GPIO) kunnen aanspreken. In dit project werd de buzzer tussen GPIO poort 11 en de grond (-) gesoldeerd. In de code hieronder kun je zien dat poort 11 een aantal maal HIGH wordt gezet voor een halve seconde.

Het filmpje hiernaast toont de buzzer (geluid aan) en de Euro-millions melder in actie. Als het filmpje niet automatisch start, klik dan op de foto. De buzzer zie je bovenaan verbonden via een groene en oranje draadje.

Veel plezier met dit project.

import urllib.request
import time, re
import xml.etree.ElementTree as ET
from sense_hat import SenseHat
import RPi.GPIO as GPIO

def getWeatherInfo():
    while True:
        try:
            response = urllib.request.urlopen('http://api.openweathermap.org/data/2.5/weather?q=mortsel,be&units=metric&mode=xml&appid=82f8742afbebf7d3c33028fd45468a7d')
            xml = response.read()
            xmlTree = ET.fromstring(xml)
            return xmlTree
        except AssertionError as error:
            print("Error from OWM: %s" % error)
            time.sleep(60)
            pass

#https://www.weather-forecast.com/locations/Mortsel/forecasts/latest
#regex: rain b-forecast__table-value">-< ... rain\sb-forecast__table-value\"\>(\W+|\d+)\<
#range(0, len(match.groups())):
def getRainInfo(NmbOfValues):
    regex = r"rain\sb-forecast__table-value\"\>(\W+|\d+)\<"
    rainInMm = []
    while True:
        try:
            response = urllib.request.urlopen('https://www.weather-forecast.com/locations/Mortsel/forecasts/latest').read()
            matches = re.finditer(regex, str(response), re.MULTILINE)
            for matchNum, match in enumerate(matches):
                matchNum = matchNum + 1
                for groupNum in range(0, len(match.groups())):
                    groupNum = groupNum + 1
                    group = match.group(groupNum)
                    #print ("Group - groupnum: %s - %s" % (str(group), str(matchNum)))
                    if str(group) == '-':
                        rainInMm.append('0')
                    else:
                        rainInMm.append(str(group))
                    if matchNum == NmbOfValues:
                        return rainInMm
        except AssertionError as error:
            print("Error from weather-forecast: %s" % error)
            time.sleep(60)
            pass

def getWeatherFromXml(xml, xmlType, xmlValue):
    if xmlType == 'wind':
        wind = xml.find(xmlType)
        speed = wind.find('speed')
        val = speed.get(xmlValue)
    else:
        temp = xml.find(xmlType)
        val = temp.get(xmlValue)
    return val

def showTempChar(sense, tempChar):
    sense.show_letter(tempChar, [0, 102, 102])

def showPosNegPixel(sense, posNeg):
    if posNeg == "Pos":
        sense.set_pixel(7, 3, 255, 0, 0)
    else:
        sense.set_pixel(7, 3, 0, 0, 255)

def showUpDownPixel(sense, temp, oldTemp, negPos, oldUpDown):
    if temp == oldTemp:
        if oldUpDown == "":
            return oldUpDown
        if oldUpDown == "Up":
            sense.set_pixel(7, 7, 0, 255, 0)
        else:
            sense.set_pixel(7, 7, 0, 128, 255)
        return oldUpDown
    date = time.strftime("%d/%m/%y %H:%M", time.localtime())
    if negPos == "Pos":
        if float(temp) > float(oldTemp):
            sense.set_pixel(7, 7, 0, 255, 0)
            oldUpDown = "Up"
        else:
            sense.set_pixel(7, 7, 0, 128, 255)
            oldUpDown = "Down"
    else:
        if float(temp) > float(oldTemp):
            sense.set_pixel(7, 7, 0, 128, 255)
            oldUpDown = "Down"
        else:
            sense.set_pixel(7, 7, 0, 255, 0)
            oldUpDown = "Up"
    print("Temp changed to %s on %s" % (temp, date))
    return oldUpDown

def showTempDecPixels(sense, value):
    valHalf = int(value)
    r = 0
    g = 102
    b = 102
    if valHalf > 4:
        sense.set_pixel(0, 7, r, g, b)
    else:
        sense.set_pixel(0, 7, 0, 0, 0)

def showTempBar(sense, posNeg, leftChar):
    if posNeg == "Pos":
        if int(leftChar) > 0:
            sense.set_pixel(7, 2, 153, 0, 0)
        if int(leftChar) > 1:
            sense.set_pixel(7, 1, 153, 0, 0)
        if int(leftChar) > 2:
            sense.set_pixel(7, 0, 153, 0, 0)
    else:
        if int(leftChar) > 0:
            sense.set_pixel(7, 4, 0, 0, 153)
        if int(leftChar) > 1:
            sense.set_pixel(7, 5, 0, 0, 153)

def showWindBar(sense, speed):
    if float(speed) > 2:
        sense.set_pixel(2, 7, 0, 153, 0)
    if float(speed) > 5:
        sense.set_pixel(3, 7, 0, 153, 0)
    if float(speed) > 8:
        sense.set_pixel(4, 7, 0, 153, 0)
    if float(speed) > 12:
        sense.set_pixel(5, 7, 0, 153, 0)

def showCloudsBar(sense, clouds):
    if clouds == '0':
        return
    cloudy = int(clouds) / 22
    if cloudy > 0:
        sense.set_pixel(2, 0, 102, 0, 102)
    if cloudy > 1:
        sense.set_pixel(3, 0, 102, 0, 102)
    if cloudy > 2:
        sense.set_pixel(4, 0, 102, 0, 102)
    if cloudy > 3:
        sense.set_pixel(5, 0, 102, 0, 102)
    if cloudy > 4:
        sense.set_pixel(6, 0, 102, 0, 102)
        
def showRainBar(sense, rainValues):
    for i in range(0, len(rainValues)):
        val = int(rainValues[i])
        if val == 0:
            sense.set_pixel(0, i, 0, 102, 0)
        if val > 0:
            sense.set_pixel(0, i, 255, 255, 102)
        if val > 1:
            sense.set_pixel(0, i, 255, 153, 51)
        if val > 3:
            sense.set_pixel(0, i, 255, 51, 51)    

def defineTempChar(temp):
    if temp[:1] == '-':
        tempPosNeg = 'Neg'
        temp = temp[1:]
    else:
        tempPosNeg = 'Pos'
    if '.' in temp:
        temp, tempDec = temp.split('.')
    else:
        tempDec = "0"
    tempDecLeft = tempDec[:1]
    tempDecInt = int(tempDec)
    if len(tempDec) == 1:
        tempDecInt *= 10
    if len(temp) > 1:
        tempLeftChar = temp[:1]
    else:
        tempLeftChar = '0'
    #print(tempLeftChar, temp[-1:], tempDecLeft, tempPosNeg)
    return tempLeftChar, temp[-1:], tempDecLeft, tempPosNeg

def isNight():
    hour = time.strftime("%H", time.localtime())
    weekday = time.strftime("%w", time.localtime())
    if int(hour) >= 0 and int(hour) < 6 and int(weekday) > 0 and int(weekday) < 6:
        return True
    if int(hour) >= 0 and int(hour) < 8 and (int(weekday) < 1 or int(weekday) > 5):
        return True

def waitLoop(waitTime):
    global SenseShow
    i = 0
    waitTime = waitTime / 5
    r, g, b = sense.get_pixel(7, 3)
    dayLotto = time.strftime("%w", time.localtime())
    #dayLotto = 2
    while True:
        time.sleep(5)
        i += 1
        if isNight():
            sense.set_pixel(7, 3, 0, 0, 153)
        else:
            sense.set_pixel(7, 3, 192, 192, 192)
        time.sleep(0.15)
        sense.set_pixel(7, 3, r, g, b)
        if ((dayLotto == 2 or dayLotto == 5) and SenseShow == False):
            hourLotto = time.strftime("%H", time.localtime())
            minLotto = time.strftime("%M", time.localtime())[-1:]
            secLotto = time.strftime("%S", time.localtime())
            # (minLotto == "0" or minLotto == "5") and hourLotto == "21" and
            if (minLotto == "0" or minLotto == "5") and hourLotto == "21" and int(secLotto) < 6:
                checkLotto()
                break
        if i == waitTime:
            break

def checkLotto():
    global SenseShow
    #dateLotto = '24-04-2020'
    dateLotto = time.strftime("%d-%m-%Y", time.localtime())
    result = getEuroMillionsResult(dateLotto)
    if result != False:
            SenseShow = True
            #print(str(result))
            activBuzzer()
            sense.show_message(result, 0.05)

def activBuzzer():
    for i in range(0, 5):
        GPIO.output(11, GPIO.HIGH)
        time.sleep(0.5)
        GPIO.output(11, GPIO.LOW)
        time.sleep(1)

def getEuroMillionsResult(date):
    regex = r">(\d+)<.*?>(\d+)<.*?>(\d+)<.*?>(\d+)<.*?>(\d+)<.*?>(\d+)<.*?>(\d+)<.*?(\d{4})-(\d{2})-(\d{2})T.*?Prize\sPer\sWinner\">&#8364;(\d{1,3}(?:,\d{3})*(?:\.\d{2})).*?Prize\sPer\sWinner\">&#8364;(\d{1,3}(?:,\d{3})*(?:\.\d{2})).*?Prize\sPer\sWinner\">&#8364;(\d{1,3}(?:,\d{3})*(?:\.\d{2})).*?Prize\sPer\sWinner\">&#8364;(\d{1,3}(?:,\d{3})*(?:\.\d{2})).*?Prize\sPer\sWinner\">&#8364;(\d{1,3}(?:,\d{3})*(?:\.\d{2})).*?Prize\sPer\sWinner\">&#8364;(\d{1,3}(?:,\d{3})*(?:\.\d{2})).*?Prize\sPer\sWinner\">&#8364;(\d{1,3}(?:,\d{3})*(?:\.\d{2})).*?Prize\sPer\sWinner\">&#8364;(\d{1,3}(?:,\d{3})*(?:\.\d{2})).*?Prize\sPer\sWinner\">&#8364;(\d{1,3}(?:,\d{3})*(?:\.\d{2})).*?Prize\sPer\sWinner\">&#8364;(\d{1,3}(?:,\d{3})*(?:\.\d{2})).*?Prize\sPer\sWinner\">&#8364;(\d{1,3}(?:,\d{3})*(?:\.\d{2})).*?Prize\sPer\sWinner\">&#8364;(\d{1,3}(?:,\d{3})*(?:\.\d{2})).*?Prize\sPer\sWinner\">&#8364;(\d{1,3}(?:,\d{3})*(?:\.\d{2})).*"
    # regex = r">(\d+)<.*?>(\d+)<.*?>(\d+)<.*?>(\d+)<.*?>(\d+)<.*?>(\d+)<.*?>(\d+)<.*?(\d{4})-(\d{2})-(\d{2})T.*"

    # Verander hieronder de cijfers en sterren waarmee u meespeelt met euro-millions
    rooster = [{1: 7, 2: 8, 3: 27, 4: 43, 5: 49, 6: 2, 7: 8, "balwin": 0, "sterwin": 0, "bedrag": 0},
               {1: 8, 2: 10, 3: 26, 4: 31, 5: 50, 6: 2, 7: 9, "balwin": 0, "sterwin": 0, "bedrag": 0},
               {1: 6, 2: 19, 3: 24, 4: 27, 5: 34, 6: 7, 7: 8, "balwin": 0, "sterwin": 0, "bedrag": 0}]

    winst = {"00": 0, "01": 0, "10": 0, "11": 0, "02": 0, "20": 13, "21": 12, "12": 11, "30": 10, "31": 9, "22": 8,
             "40": 7, "32": 6, "41": 5, "42": 4, "50": 3, "51": 2, "52": 1}
    lijst = []
    lijst.append("0")
    SenseStr = "Geen winst"

    try:
        url = 'https://www.euro-millions.com/results/' + date
        response = urllib.request.urlopen(url).read()
        matches = re.finditer(regex, str(response), re.MULTILINE | re.DOTALL)
        for matchNum, match in enumerate(matches):
            for groupNum in range(0, len(match.groups())):
                groupNum = groupNum + 1
                group = match.group(groupNum)
                # print("Group - groupnum: %s - %s" % (str(group), str(groupNum)))
                if groupNum > 10:
                    w = str(group).replace(".00", "")
                    w = str(w).replace(",", "")
                    # w = str(w).replace(".", ",")
                    lijst.append(str(w))
                elif groupNum < 6:
                    for roosterNum in range(0, 3):
                        for balNum in range(1, 6):
                            if int(group) == rooster[roosterNum][balNum]:
                                rooster[roosterNum]['balwin'] += 1
                elif groupNum > 5 and groupNum < 8:
                    for roosterNum in range(0, 3):
                        for balNum in range(6, 8):
                            # print(group)
                            # print(rooster[roosterNum][balNum])
                            if int(group) == rooster[roosterNum][balNum]:
                                rooster[roosterNum]['sterwin'] += 1
            # print(lijst)
            # print(rooster)

            for roosterNum in range(0, 3):
                balsterwin = str(rooster[roosterNum]['balwin']) + str(rooster[roosterNum]['sterwin'])
                winstindex = winst[balsterwin]
                windstbedrag = lijst[winstindex]
                rooster[roosterNum]['bedrag'] += float(windstbedrag)
                # print(rooster[roosterNum])
                if rooster[roosterNum]['bedrag'] > 0:
                    if SenseStr == "Geen winst":
                        SenseStr = "Gewonnen: " + str(rooster[roosterNum]['balwin']) + " + " + str(
                            rooster[roosterNum]['sterwin']) + " = " + str(rooster[roosterNum]['bedrag'])
                    else:
                        SenseStr = SenseStr + " - " + str(rooster[roosterNum]['balwin']) + " + " + str(
                            rooster[roosterNum]['sterwin']) + " = " + str(rooster[roosterNum]['bedrag'])

            return SenseStr
    except:
        return False


sense = SenseHat()
xmlWeather = getWeatherInfo()
rainMm = getRainInfo(6)

GPIO.setmode(GPIO.BOARD)  # Numbers GPIOs by physical location
GPIO.setwarnings(False)
GPIO.setup(11, GPIO.OUT)  # Set buzzerPin's mode is output

#rainMm = ['0', '1', '10', '1', '2', '8']
#print (rainMm)
temp = getWeatherFromXml(xmlWeather, 'temperature', 'value')
wind = getWeatherFromXml(xmlWeather, 'wind', 'value')
clouds = getWeatherFromXml(xmlWeather, 'clouds', 'value')
date = time.strftime("%d/%m/%y %H:%M", time.localtime())
#print ("Date=%s, Temp=%s, Wind=%s, Clouds=%s, Rain=%s" % (str(date), str(temp), str(wind), str(clouds), str(rainMm)))
tempOld = temp
UpDown = ""

SenseShow = False

while True:
    tempLeftChar, tempRightChar, tempDecLeft, tempPosNeg = defineTempChar(temp)
    if isNight():
        sense.clear()
        SenseShow = False
    else:
        showTempChar(sense, tempRightChar)
        UpDown = showUpDownPixel(sense, temp, tempOld, tempPosNeg, UpDown)
        tempOld = temp
        showPosNegPixel(sense, tempPosNeg)
        showTempDecPixels(sense, tempDecLeft)
        showCloudsBar(sense, clouds)
        showTempBar(sense, tempPosNeg, tempLeftChar)
        showWindBar(sense, wind)
        showRainBar(sense, rainMm)
    waitLoop(600)
    if not isNight():
        xmlWeather = getWeatherInfo()
        rainMm = getRainInfo(6)
        temp = getWeatherFromXml(xmlWeather, 'temperature', 'value')
        wind = getWeatherFromXml(xmlWeather, 'wind', 'value')
        clouds = getWeatherFromXml(xmlWeather, 'clouds', 'value')