Projekt Kesselsteuerung mit Raspberry Pi

Es gibt 27 Antworten in diesem Thema, welches 22.724 mal aufgerufen wurde. Der letzte Beitrag () ist von peterpappnase.

  • Hallo Forianer,
    ich wurde gefragt ob ich bereit wäre, mein Raspy Projekt hier im Forum vorzustellen. Die kurze Antwort zu dieser Frage ist: Ja. Ich habe keinerlei Probleme damit, die verwendete Hardware vorzustellen und auch die Software zu veröffentlichen, wenn Interesse daran besteht. Ich hoffe, ich trete damit niemandem auf die Füße. Niemand sollte meinen, dass das Programm schon Massentauglich ist. Das Programm ist zunächst mal funktional um einen Abbrand damit zu regeln. Nebenher werden auch die RLA und Heizungsmischer sowie die RLA Pumpe damit angesteuert. Warmwasser und Heizkreispumpe wird noch folgen. Fehlerabfragen sind nicht viele vorhanden. Kurz gesagt, am Programm muss noch einiges gemacht werden, damit es auch jemand ohne Programmierkenntnisse einsetzen kann. Ich habe für dieses Programm Python verwendet, weil es schön einfach ist und die Funktionen nicht zeitkritisch sind. Wer Python nicht kennt aber etwas programmieren kann, sollte sich nicht scheuen mal einen Blick auf Python zu werfen. Ich habe Python vorher selbst noch nie eingesetzt, bin aber jetzt begeistert davon.
    Als Hardware habe ich einen Rasperry Pi-B. Die A Version sollte auch funktionieren, wenn man keinen Ethernet Anschluss braucht. Allerdings hat der Raspy wohl einige Bugs in den Wifi Modulen. Bei meinen ersten Versuchen ist er mir immer innerhalb von 24h hängen geblieben. Man könnte das beheben, indem man einen Watch-dog programmiert, der immer mal wieder den Router im Haus an-pingt und bei mangelnder Antwort einen Reset der Wifi Verbindung durchführt. Ich habe mich jedoch für Ethernet-Adapter entschieden, die die Stromleitung zur Übertragung nutzen.
    Zur Ansteuerung der Pumpen etc. verwenden ich eine Solid-State Relais Karte mit 8 SSR. Bei meinem derzeitigen Setup würden es aber auch ganz normale mechanische Relais tun. Zur Steuerung der Lambda-Sonde verwende ich die Schaltung von Sebastian Knödler. Im Moment kommuniziert der Raspi per serielle Schnittstelle mit der Lambda Schaltung. Das will ich jedoch ändern und zukünftig die Analogsignale verwenden. Dadurch kann man sich die extra Schaltung sparen, die notwendig ist um die seriellen 5V Signale des Raspi auf 12V Signale für die Lambda Schaltung umzuwandeln. Für die Auswertung des analogen Signals des PT1000 (Rauchrohr) verwende ich einen Arduino. Der liefert auch den PWM Takt für die 2x 12V Lüfter, die ich verwende. Das hat sich jedoch nicht bewährt. Der Arduino läuft nicht stabil, er hängt sich manchmal auf. Das scheint ein bekanntes Problem zu sein. Um das zu umgehen habe ich einen Watch-Dog verwendet, der dem Arduino einen Reset schickt, wenn er sich innerhalb einer gewissen Zeitspanne nicht meldet. Dadurch wird aber auch das PWM Signal zurückgesetzt, was dazu führt, dass die Gebläse kurz aufjaulen, bis die nächste Frequenz auf den Arduino geschickt wird. Das passiert zwar nicht oft, aber schon so ein Mal am Tag. Der Raspi kann auch PWM. Somit werde ich dem Raspi in Zukunft die Lüftersteuerung direkt übergeben. Ich wollte Pins sparen, hat aber nicht funktioniert. Den Arduino möchte ich mittelfristig durch einen A/D Wandler ersetzen. Als Netztteil verwende ich ein größeres 12V Netzteil, welches die ganze Schaltung incl. Lüfter versorgt. Für 5V verwende ich einen DC/DC Wandler. Allerdings kann man das wahrscheinlich einfacher machen, wenn man ein Computernetzteil verwendet. Die liefern 12V und 5V. Im Moment läuft die gesamte Kommunikation zum Raspy über SSH. Ich habe bereits ein Display bestellt, auf dem ich die wichtigen Daten direkt am Kessel ausgeben will.
    So weit zu meinem Setup. Prinzipiell ist es denkbar, dass die Steuerung beliebig erweiterbar ist. Mit einem Raspy B+ hätte man auch mehr GPIO Pins zur Verfügung, somit könnten noch einige weitere Relais für Pumpen etc. schalten. Auch an HKS habe ich schon mal gedacht.
    Allerdings bin ich mir nicht sicher, ob der ganze Aufbau nach VDE/DIN/ISO/EU/Schlagmichtot, den gültigen Normen entspricht. Ich bin weder Elektroniker noch Programmierer. Ich mache das alles nur als Hobby. Leute, die es besser wissen als ich, dürfen sich gerne mit Verbesserungen einbringen.
    Zur Speicherung der Daten sende ich die Daten an Google Sheet. Dort werden sie in eine Tabelle eingetragen und können als Graphen aufbereitet werden, wie bei Excel. Allerdings ist Google Sheet dynamisch. D.h., der Graph ändert sich automatisch, wenn im Hintergrund weitere Daten eingefügt werden.
    Ich hatte ursprünglich nicht vor, die gesamte Kesselsteuerung selbst zu programmieren. Ich habe mit das eigentlich schwieriger vorgestellt. Vor 2 Jahren habe ich mit dem Raspi angefangen, als mir die Steuerung der Ölheizung verreckt ist. Nachdem ich ein Angebot über €1300 für eine neue Steuerung bekam, habe ich mir Gedanken gemacht, wie man das günstiger lösen kann. Die Heizung lief ein paar Wochen manuell gesteuert und seit fast 2 Jahren läuft der Raspy ohne Probleme. Das Ziel für den Orlan war, die Mischer und die Pumpen und Thermofühler mit dem Raspy zu steuern bzw. abzufragen. Die RLA Regelung, die ich geschrieben hatte gefiel mir nicht, deshalb habe ich eine bereits fertige PID Python Klasse eingesetzt. Das hat wunderbar funktioniert. Da lag es nahe, die PID Klasse mal am Primärlüfter auszuprobieren. Das hat auch relativ schnell funktioniert. Dadurch habe ich mich dann auch an die O2 Regelung heran getraut und es funktioniert, meiner Meinung nach sehr gut.


    Ich bin nun mal Gespannt auf euer Feedback. Wenn kein weiteres Interesse an dieser Lösung besteht, halte ich mich zurück. Falls Interesse besteht, poste ich das Python Skript im derzeitigen Zustand. Es wird sicherlich Erklärungen benötigen. Am liebsten wäre mir, wenn sich hier eine kleine Entwicklergruppe bilden würde, um Hard- und Software weiter zu entwickeln. Vielleicht kommen wir ja mal irgendwann zu dem Punkt, dass die Raspy Steuerung Massentauglich wird.


    VG
    Ralf

  • Hallo Ralf,


    danke für den Abstract. :)


    Mir brannte es auch schon unter den Fingern dich danach zu fragen.


    Ich bin an den Python Code interessiert. Und sei es nur um zu lernen. Für mehr muss erst mal meine Hardware stehen.


    MbG


    Daniel

    15600l Puffer mit integrierten 300l VA WW-Boiler, gebrauchter Lopper Drummer 50

  • Ich hab den Banana PI liegen , Python läuft ja unter Linux, hab lubuntu auf die SD gespeilt,
    mit XP selbstladend gemacht. Zeit, Zeit, noch vieles andere wartet.
    Grüße Martin

    Soli 25 E 2000 l Puffer Solar 8,9m2 Röhren
    Plattentaush. Friwa eigenbau
    Flamtronik 2Gebl. Konr. Dimmer.
    HKS u. EigenDüse Gr. Brennk. Eigenbau
    CO Messung

  • #!/usr/bin/python


    import os.path
    import time
    import datetime
    import threading
    import serial
    import RPi.GPIO as GPIO



    GPIO.setmode(GPIO.BOARD) ## Use board pin numbering
    GPIO.setwarnings(False) ## Don't show GPIO warnings
    GPIO.setup(12, GPIO.OUT) ## Setup SSD 5 to OUT
    GPIO.setup(23, GPIO.OUT) ## Setup SSD 6 to OUT
    GPIO.setup(13, GPIO.OUT) ## Setup SSD 8 to OUT
    GPIO.setup(11, GPIO.OUT) ## Setup SSD 7 to OUT
    GPIO.setup(15, GPIO.OUT) ## Setup SSD 4 to OUT
    GPIO.setup(16, GPIO.OUT) ## Setup SSD 3 to OUT
    GPIO.setup(18, GPIO.OUT) ## Setup SSD 2 to OUT
    GPIO.setup(22, GPIO.OUT) ## Setup SSD 1 to OUT


    GPIO.output(12,True) ## SSD 5
    GPIO.output(23,False) ## SSD 6
    GPIO.output(13,False) ## SSD 8
    GPIO.output(11,False) ## SSD 7
    GPIO.output(15,False) ## SSD 4
    GPIO.output(16,False) ## SSD 3
    GPIO.output(18,False) ## SSD 2
    GPIO.output(22,False) ## SSD 1


    #Pin 16 = Relais 3
    #Pin 15 = Relais 4
    #Pin 15 = Relais 4
    #Pin 23 = Relais 6
    #Pin 11 = Relais 7
    #Pin 13 = Relais 8


    # ===========================================================================
    # Class LambdaControl
    # ===========================================================================
    class LambdaControl:
    def __init__(self):
    try:
    self.ser = serial.Serial(port='/dev/ttyAMA0', baudrate=115200,parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=1)
    except: #If COM port can't be opened the Class will return values -1 all the time
    self.O2 = str("{:2.3f}".format(-1))
    return(False)
    self.ser.flushInput()
    self.stopSig = 0
    self.lambdaStatus = 0
    self.lambdaProbeOff()
    self.readLambda()


    def readLambda(self):
    while True: #Sometimes a partial line gets send so we need this loop
    line = self.ser.readline()
    temp_data = line.split(";")
    if len(temp_data) == 4:
    break
    try:
    temp = float(temp_data[0])
    except:
    temp = 0
    if int(temp_data[1]) != 160:
    self.O2 = ""
    else:
    self.O2 = str("{:2.1f}".format(temp/10))
    self.timerThread = threading.Timer(0.1, self.readLambda) # Time is not important Lambda module will only give data every 0.5s
    if(self.stopSig == 0):
    self.timerThread.start()


    def lambdaProbeOn(self):
    self.ser.write("\rH\r")
    self.lambdaStatus = 1


    def lambdaProbeOff(self):
    self.ser.write("\rD\r")
    self.lambdaStatus = 0


    def getRestO2(self):
    return(self.O2)


    def getLambdaStatus(self):
    return(self.lambdaStatus)


    def __del__(self):
    self.lambdaProbeOff()
    self.stopSig = 1
    # ===========================================================================
    # End of Class Lambda Control
    # ===========================================================================


    # ===========================================================================
    # Class Pt1000
    # ===========================================================================
    from nanpy import Arduino
    from nanpy.watchdog import Watchdog


    class Pt1000:
    def __init__(self):
    self.setWatchdog()
    self.readA0()


    def readA0(self):
    avalue = Arduino.analogRead(0)
    self.wd.reset() #Reset watchdog timer when Arduino is still running
    self.pt1000_temp = (avalue/2) - 32
    self.timerThread = threading.Timer(0.5, self.readA0)
    self.timerThread.start()


    def getPt1000(self):
    return(self.pt1000_temp)


    def setWatchdog(self): #Set watchdog timer to 1s so the Arduino resets if it does not
    #receive the reset within that time
    self.wd = Watchdog(connection=None)
    self.wd.disable()
    self.wd.reset()
    self.wd.enable(self.wd.WDTO_1S)



    def __del__(self):
    self.timerThread.cancel()
    # ===========================================================================
    # End of Class Pt1000
    # ===========================================================================


    # ===========================================================================
    # Class ConsoleInput
    # ===========================================================================
    from nanpy.arduinotree import ArduinoTree


    class ConsoleInput:
    def __init__(self):
    self.a = ArduinoTree()
    self.pin9 = self.a.pin.get(10)
    self.pin9.pwm.divisor = 1
    self.timerThread = threading.Timer(0.1, self.getInput)
    self.timerThread.start()


    def getInput(self):
    input_var = input("Motor speed (0-255): ")
    self.pin9.pwm.write_value(input_var)
    self.timerThread = threading.Timer(0.1, self.getInput)
    self.timerThread.start()


    # ===========================================================================
    # Class BurnerControl
    # ===========================================================================
    from nanpy.arduinotree import ArduinoTree


    class BurnerControl:
    def __init__(self, Primary_P, Primary_I, Primary_D, Primary_Timer, Secondary_P, Secondary_I, Secondary_D, Secondary_Timer, Exhaust_Temp_Class, LambdaClass):
    self.startupTime = 120
    self.startupSpeed = 20
    self.startupTemp = 100
    self.shutoffTemp = 100
    self.lambdaTakeBack = 15
    self.lambdaClass = LambdaClass
    self.heatupStep = 5
    self.maxPrimarySpeed = 100
    self.maxSecondarySpeed = 100
    self.primaryManualMode = 0
    self.primaryManualSpeed = 50
    self.targetTemp = 175
    self.targetLambda = 4
    self.a = ArduinoTree()
    self.pin9 = self.a.pin.get(9)
    self.pin10 = self.a.pin.get(10)
    self.pin9.pwm.divisor = 1
    self.primarySpeed = 0
    self.secondarySpeed = 0
    self.setPrimarySpeed(0)
    self.setSecondarySpeed(0)
    self.pPID=PID(Primary_P, Primary_I, Primary_D)
    self.sPID=PID(Secondary_P, Secondary_I, Secondary_D)
    self.pPID.setPoint(self.targetTemp)
    self.sPID.setPoint(self.targetLambda)
    self.pTimer = Primary_Timer
    self.sTimer = Secondary_Timer
    self.eTempClass = Exhaust_Temp_Class
    self.eTemp = self.eTempClass.getPt1000()
    self.startupBurner()
    self.pidLambda()


    def startupBurner(self):
    self.setPrimarySpeed(self.startupSpeed)
    self.startTimer(self.startupTime, self.heatupBurner)

    def heatupBurner(self):
    self.eTemp = self.eTempClass.getPt1000()
    if self.eTemp >= self.targetTemp - 5:
    self.pidBurner()
    return
    try:
    l = float(self.lambdaClass.getRestO2())
    except:
    l = self.targetLambda
    newSpeed = self.getPrimarySpeed()
    if l < 10:
    newSpeed = self.getPrimarySpeed() + 1
    self.setPrimarySpeed(newSpeed)
    self.startTimer(5, self.heatupBurner)

    def pidBurner(self):
    self.eTemp = self.eTempClass.getPt1000()
    if self.primaryManualMode == 1:
    newSpeed = self.primaryManualSpeed
    else:
    pid = self.pPID.update(self.eTemp)
    newSpeed = self.primarySpeed + pid
    self.setPrimarySpeed(newSpeed)
    try:
    l = float(self.lambdaClass.getRestO2())
    except:
    l =0
    if l > self.lambdaTakeBack:
    self.reduceLambdaBurner()
    return
    self.startTimer( self.pTimer, self.pidBurner)


    def pidLambda(self):
    try:
    O2 = float(self.lambdaClass.getRestO2())
    pid = self.sPID.update(O2)
    newSpeed = self.secondarySpeed + pid
    if newSpeed > self.maxSecondarySpeed:
    newSpeed = self.maxSecondarySpeed
    if newSpeed < 10:
    newSpeed = 10
    except:
    newSpeed = 20 #Emergency mode if Lambda is not giving numbers
    self.setSecondarySpeed(newSpeed)
    self.startSecondaryTimer(self.sTimer, self.pidLambda)


    def reduceLambdaBurner(self):
    self.eTemp = self.eTempClass.getPt1000()
    try:
    l = float(self.lambdaClass.getRestO2())
    except:
    l =0
    if l < self.lambdaTakeBack - 1:
    self.startTimer(self.pTimer, self.pidBurner)
    return
    self.setPrimarySpeed(25)
    self.setSecondarySpeed(10)
    if self.eTemp < self.shutoffTemp:
    self.setPrimarySpeed(0)
    try:
    self.secondaryTimerThread.cancel()
    except:
    pass
    self.setSecondarySpeed(0)
    else:
    self.startSecondaryTimer(self.pTimer, self.reduceLambdaBurner)


    def setPrimarySpeed(self, percent):
    if percent > self.maxPrimarySpeed:
    percent = self.maxPrimarySpeed
    if percent < 15:
    percent = 15
    speed = percent * 2.55
    self.pin9.pwm.write_value(int(round(speed)))
    self.primarySpeed = int(round(percent))


    def setSecondarySpeed(self, percent):
    speed = percent * 2.55
    self.pin10.pwm.write_value(int(round(speed)))
    self.secondarySpeed = int(round(percent))

    def startTimer(self, timer, call_func):
    self.timerThread = threading.Timer(timer, call_func)
    self.timerThread.start()

    def startSecondaryTimer(self, timer, call_func):
    self.secondaryTimerThread = threading.Timer(timer, call_func)
    self.secondaryTimerThread.start()

    def getPrimarySpeed(self):
    return(self.primarySpeed)


    def getSecondarySpeed(self):
    return(self.secondarySpeed)


    def exist(self): #Used to determine if class is running
    return(True)


    def setParameters(self, startupTime = 120, startupSpeed = 20):
    self.startupTime = startupTime #Time to allow the fire to start burning in seconds
    self.startupSpeed = startupSpeed #Blower speed during startupTime
    self.startupTemp = 100
    self.shutoffTemp = 100
    self.lambdaTakeBack = 15
    self.lambdaClass = LambdaClass
    self.heatupStep = 5
    self.maxPrimarySpeed = 75
    self.maxSecondarySpeed = 60
    self.targetTemp = 140
    self.targetLambda = 4
    self.pPID=PID(Primary_P, Primary_I, Primary_D)
    self.sPID=PID(Secondary_P, Secondary_I, Secondary_D)
    self.pPID.setPoint(self.targetTemp)
    self.sPID.setPoint(self.targetLambda)
    self.pTimer = Primary_Timer
    self.sTimer = Secondary_Timer

    def __del__(self):
    self.timerThread.cancel()
    self.secondaryTimerThread.cancel()
    # ===========================================================================
    # End of Class BurnerControl
    # ===========================================================================


    # ===========================================================================
    # Class OneWire
    # ===========================================================================
    class OneWire:
    def __init__(self, file):
    self.file = file
    self.temperature = -999
    self.readTemp()



    def readTemp(self):
    myfile = open(self.file)
    text = myfile.read()
    myfile.close()
    if text.find("NO") == -1:
    temp_data = text.split()[-1]
    temp = float(temp_data[2:])
    if temp > 0:
    self.temperature = temp / 1000
    self.timerThread = threading.Timer(0.5, self.readTemp)
    self.timerThread.start()


    def getTemp(self):
    return(self.temperature)


    def __del__(self):
    self.timerThread.cancel()
    # ===========================================================================
    # End of Class OneWire
    # ===========================================================================


    # ===========================================================================
    # Class PID
    # ===========================================================================


    #The recipe gives simple implementation of a Discrete Proportional-Integral-Derivative (PID) controller. PID controller gives output value for error between desired reference input and measurement feedback to minimize error value.
    #More information: http://en.wikipedia.org/wiki/PID_controller
    #
    #cnr437@gmail.com
    #
    ####### Example #########
    #
    #p=PID(3.0,0.4,1.2)
    #p.setPoint(5.0)
    #while True:
    # pid = p.update(measurement_value)
    #
    #



    class PID:
    """
    Discrete PID control
    """


    def __init__(self, P=2.0, I=0.0, D=1.0, Derivator=0, Integrator=0, Integrator_max=500, Integrator_min=-500):


    self.Kp=P
    self.Ki=I
    self.Kd=D
    self.Derivator=Derivator
    self.Integrator=Integrator
    self.Integrator_max=Integrator_max
    self.Integrator_min=Integrator_min


    self.set_point=0.0
    self.error=0.0


    def update(self,current_value):
    """
    Calculate PID output value for given reference input and feedback
    """


    self.error = self.set_point - current_value


    self.P_value = self.Kp * self.error
    self.D_value = self.Kd * ( self.error - self.Derivator)
    self.Derivator = self.error


    self.Integrator = self.Integrator + self.error


    if self.Integrator > self.Integrator_max:
    self.Integrator = self.Integrator_max
    elif self.Integrator < self.Integrator_min:
    self.Integrator = self.Integrator_min


    self.I_value = self.Integrator * self.Ki


    PID = self.P_value + self.I_value + self.D_value
    return PID


    def setPoint(self,set_point):
    """
    Initilize the setpoint of PID
    """
    self.set_point = set_point
    self.Integrator=0
    self.Derivator=0


    def setIntegrator(self, Integrator):
    self.Integrator = Integrator


    def setDerivator(self, Derivator):
    self.Derivator = Derivator


    def setKp(self,P):
    self.Kp=P


    def setKi(self,I):
    self.Ki=I


    def setKd(self,D):
    self.Kd=D


    def getPoint(self):
    return self.set_point


    def getError(self):
    return self.error


    def getIntegrator(self):
    return self.Integrator


    def getDerivator(self):
    return self.Derivator


    # ===========================================================================
    # End of Class PID
    # ===========================================================================


    # ===========================================================================
    # Class MixerControl
    # ===========================================================================


    class MixerControl:
    def __init__(self, mytc, Target, P, I, D, timer, GPIO_warm, GPIO_cold):
    self.myTempClass = mytc
    self.TargetTemp = Target
    self.p=PID(P, I, D)
    self.timer = timer
    self.pin_warm = GPIO_warm
    self.pin_cold = GPIO_cold
    self.p.setPoint(Target)
    self.mixerControl()


    def mixerCold(self, secs):
    GPIO.output(self.pin_warm, False)
    GPIO.output(self.pin_cold, True)
    self.timerThread = threading.Timer(secs, self.mixerOff)
    self.timerThread.start()


    def mixerWarm(self, secs):
    GPIO.output(self.pin_cold, False)
    GPIO.output(self.pin_warm,True)
    self.timerThread = threading.Timer(secs, self.mixerOff)
    self.timerThread.start()


    def mixerOff(self):
    GPIO.output(self.pin_cold,False)
    GPIO.output(self.pin_warm,False)


    def setRHUon(self):
    self.timerThread.cancel()
    self.thr.cancel()
    self.mixerCold(45)
    GPIO.output(12,False) ## Rest Heat Usage Ball Valve closed


    def setRHUoff(self):
    self.timerThread.cancel()
    self.thr.cancel()
    self.mixerWarm(45)
    GPIO.output(12,True) ## Rest Heat Usage Ball Valve open

    def mixerControl(self):
    actTemp = self.myTempClass.getTemp()
    pid = self.p.update(actTemp)
    if pid > 0:
    if pid > 30:
    pid = 30
    self.mixerWarm(pid)
    else:
    if pid < -30:
    pid = -30
    self.mixerCold(pid * -1)


    self.thr = threading.Timer(self.timer, self.mixerControl)
    self.thr.start()


    def __del__(self):
    self.timerThread.cancel()
    self.thr.cancel()


    # ===========================================================================
    # End of Class MixerControl
    # ===========================================================================


    # ===========================================================================
    # Class NewDatalogger
    # ===========================================================================
    import gdata.spreadsheet.service
    import gdata.docs.client


    class NewDatalogger:
    def __init__(self):
    self.openSpreadsheet()


    def insertRow(self, values):
    tday = datetime.date.weekday(datetime.date.today())
    if(tday != self.lastDay):
    self.openSpreadsheet()
    row = { "a": values[0], "b": values[1], "c": values[2], "d": values[3], "e": values[4], "f": values[5], "g": values[6], "h": values[7], "i": values[8], "j": values[9], "k": values[10], "l": values[11], "m": values[12] }
    self.gs.InsertRow(row, self.spreadsheet_key, self.worksheetID)


    def openSpreadsheet(self):
    self.email = 'YOUR_ACCOUNT@gmail.com'
    self.password = 'YOUR_PASSWORD'
    self.source = 'GasPi'
    self.worksheetID = 'od6'
    foldername = 'GasPi'
    tday = datetime.date.today()
    self.lastDay = datetime.date.weekday(tday)
    filename = 'GasPi Log ' + str(tday)
    gc = gdata.docs.client.DocsClient()
    gc.ClientLogin(self.email, self.password, self.source)


    try: #Try open existing folder
    q = gdata.docs.client.DocsQuery(title=foldername, title_exact='true', show_collections='true')
    folder = gc.GetResources(q=q).entry[0]
    contents = gc.GetResources(uri=folder.content.src)
    except: #Create new folder
    folder = gdata.docs.data.Resource('folder', foldername)
    contents = gc.CreateResource(folder)
    q = gdata.docs.client.DocsQuery(title=foldername, title_exact='true', show_collections='true')
    folder = gc.GetResources(q=q).entry[0]
    contents = gc.GetResources(uri=folder.content.src)


    try: #Try open existing spreadsheet
    q = gdata.docs.client.DocsQuery(title=filename, title_exact='true', show_collections='true')
    document = gc.GetResources(q=q).entry[0]
    self.spreadsheet_key = document.GetId().split("%3A")[1]
    self.ssLogin()
    except:
    try: #Copy Template
    q = gdata.docs.client.DocsQuery(title='GasPi Log Template', title_exact='true', show_collections='true')
    document = gc.GetResources(q=q).entry[0]
    copy = gc.CopyResource(document, filename)
    gc.MoveResource(copy, folder, False) #For some reason it does not really move it, just a symlink
    self.spreadsheet_key = copy.GetId().split("%3A")[1]
    self.ssLogin()
    except: #Create new spreadsheet
    document = gdata.docs.data.Resource('spreadsheet', filename)
    document = gc.CreateResource(document, collection=folder)
    self.spreadsheet_key = document.GetId().split("%3A")[1]
    self.ssLogin()
    headers = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m"]
    for i, header in enumerate(headers):
    entry = self.gs.UpdateCell(row=1, col=i+1, inputValue=header, key=self.spreadsheet_key, wksht_id=self.worksheetID)
    row = { "a": "Time", "b": "BasementTemperature", "c": "Oxygen", "d": "ExhaustTemp", "e": "GasifierWaterExitTemp", "f": "OutsideTemp", "g": "MixerForward", "h": "MixerReturn", "i": "PrimaryBlowerSpeed", "j": "SecondaryBlowerSpeed", "k": "Tank1Top", "l": "Tank1Middle", "m": "Tank1Bottom"}
    self.gs.InsertRow(row, self.spreadsheet_key, self.worksheetID)


    def ssLogin(self):
    self.gs = gdata.spreadsheet.service.SpreadsheetsService()
    self.gs.email = self.email
    self.gs.password = self.password
    self.gs.source = self.source
    self.gs.ProgrammaticLogin()
    # ===========================================================================
    # End of NewDatalogger
    # ===========================================================================


    # ===========================================================================
    # Main Code
    # ===========================================================================
    stopSig = 0
    a = ArduinoTree()
    pin9 = a.pin.get(9)
    pin9.pwm.divisor = 1
    pin9.pwm.write_value(0)
    pin10 = a.pin.get(10)
    pin10.pwm.divisor = 1
    pin10.pwm.write_value(0)


    bexitt = OneWire("/sys/bus/w1/devices/28-000004abfb55/w1_slave") # Boilerwater exit temp
    bentryt = OneWire("/sys/bus/w1/devices/28-000005e5ccc8/w1_slave") # Boilerwater entry temp
    mixreturn = OneWire("/sys/bus/w1/devices/28-000005143eb4/w1_slave") # Mixer heating water forward temp
    mixforward = OneWire("/sys/bus/w1/devices/28-000005f403dc/w1_slave") #Mixer heating return temp
    tank1top = OneWire("/sys/bus/w1/devices/28-000005f1ff79/w1_slave") #Buffer Tank 1 top
    tank1middle = OneWire("/sys/bus/w1/devices/28-000005f2fe6f/w1_slave") #Buffer Tank 1 middle
    tank1bottom = OneWire("/sys/bus/w1/devices/28-000005f2f56c/w1_slave") #Buffer Tank 1 bottom


    ric = MixerControl(bentryt, 80, 0.5, 0.01, 7, 60, 22, 18)
    roomHeat = MixerControl(mixforward, 47, 1, 0.1, 1, 30, 15, 16)
    lc = LambdaControl() #This will start reading the lambda board every 0.5s
    pt = Pt1000()
    nd = NewDatalogger()
    #ci = ConsoleInput()


    GPIO.output(23,False) ## RLA
    #Pin 16 = Relais 3
    #Pin 15 = Relais 4
    #Pin 15 = Relais 4
    #Pin 23 = Relais 6 RLA Pump
    #Pin 11 = Relais 7
    #Pin 13 = Relais 8


    while(True):
    try:
    pSpeed = bc.getPrimarySpeed()
    sSpeed = bc.getSecondarySpeed()
    except:
    if pt.getPt1000() > 50: #Start BurnerControll if exhaust temp is above 30C
    bc = BurnerControl(0.4, 0.01, 3, 10, 3, 0, 7, 8, pt, lc)
    lc.lambdaProbeOn()
    GPIO.output(23, True) #RLA Pump
    pSpeed = bc.getPrimarySpeed()
    sSpeed = bc.getSecondarySpeed()
    else:
    pSpeed = 0
    sSpeed = 0


    try:
    values = []
    values.append(str(datetime.datetime.time(datetime.datetime.now()))[:8])
    values.append("")
    values.append(lc.getRestO2().replace(".", ","))
    values.append(str(pt.getPt1000()))
    values.append(str("{:3.1f}".format(bentryt.getTemp())).replace(".", ","))
    values.append(str("{:3.1f}".format(bexitt.getTemp())).replace(".", ","))
    values.append(str("{:3.1f}".format(mixforward.getTemp())).replace(".", ","))
    values.append(str("{:3.1f}".format(mixreturn.getTemp())).replace(".", ","))
    values.append(str(pSpeed))
    values.append(str(sSpeed))
    values.append(str("{:3.1f}".format(tank1top.getTemp())).replace(".", ","))
    values.append(str("{:3.1f}".format(tank1middle.getTemp())).replace(".", ","))
    values.append(str("{:3.1f}".format(tank1bottom.getTemp())).replace(".", ","))


    if os.path.isfile('/home/pi/gaspi.temp'):
    file = open('/home/pi/gaspi.temp', 'r')
    try:
    for line in file:
    list = line.split(';')
    nd.insertRow(list)
    os.remove('/home/pi/gaspi.temp')
    file.close()
    except:
    file.close()


    try:
    nd.insertRow(values)
    except:
    with open('/home/pi/gaspi.temp', 'a') as f:
    f.write(';'.join([str(x) for x in values]) + '\n')


    for i in range(18):
    time.sleep(0.5)
    if(stopSig == 1):
    break
    if(stopSig == 1):
    break


    except (KeyboardInterrupt, SystemExit):
    print "\nReceived keyboard interrupt, quitting threads."
    lc.__del__()
    del lc
    bc.__del__()
    del bc
    ric.__del__()
    del ric
    pt.__del__()
    del pt
    del bexitt
    del bentryt
    del mixreturn
    del mixforward
    del nd
    stopSig = 1

  • Erklärung zum Programmaufbau:
    Ich verwenden in dem Programm mehrere Klassen, die alle ihre eigene kleine Aufgabe haben. Die meisten Klassen müssen nur einmal aufgerufen werden und danach führen sie sich selbständig weiter aus während das Hauptprogramm läuft. Im Prinzip ist das eine Art Multithreading. Das hat für mich den Vorteil, dass das Hauptprogramm nicht auf die Eingabe des neuesten Wertes von der entsprechenden Hardware warten muss. Die Lambda Platine gibt z.B. alle 0,5s einen neuen Wert per serielle Schnittstelle aus. Wenn es dumm laufen würde, könnte es sein, dass das Hauptprogramm gerade ein wenig zu spät ist, den letzten Wert abzufangen. Es müsste dann fast eine halbe Sekunde auf den nächsten Wert warten. Da sich das möglicherweise bei den verschiedenen Sensoren unkontrolliert aufaddieren kann, habe ich mich für diesen Weg der Programmierung entschieden. Die Klasse frischt periodisch eine Variable auf, die vom Hauptprogramm ohne große Zeitverzögerung abgefragt werden kann. Im dümmsten Fall ist der Wert allerdings um eine gewisse Zeitspanne veraltet. In der Brennersteuerung habe ich 2 Timer verwendet. Einer läuft durch die Programmteile für die Primärregelung, der Andere durch die Sekundärregelung, eigentlich ganz simpel, wenn man es weiß.
    Das Hauptprogramm holt eigentlich nur die Daten von den einzelnen Klassen ab, überwacht den Rauchrohr Pt1000 und entscheidet ob ein Brennerstart erfolgt ist. Hierfür will ich noch einen Schalter oder Taster einbauen um den Kesselstart manuell zu signalisieren. Ansonsten werden die Daten zur Speicherung aufbereitet und an Google geschickt. Auf meinem Google Drive habe ich eine Vorlage gespeichert, die verwendet wird, wenn sie gefunden wird. Da sind die Graphen schon vorbereitet
    Das langsamste am ganzen Programm ist die Datenübermittlung zu Google. Leider klappt die Übermittlung auch nicht zu 100%. Allerdings erfüllt es meine Anforderungen. Man könnte die Daten auch ganz simpel auf einer Festplatte speichern. Die SD Karte würde ich für die Datenspeicherung nicht verwenden. Die haben wohl eine begrenzte Wiederbeschreibbarkeit. Außerdem sind sie empfindlich. Ich habe schon ein paar in den Müll schmeißen müssen. Meine derzeitige SD zickt auch schon wieder rum.
    Den größten Teil der Zeit schläft das Hauptprogramm, nämlich 9x0.5 Sekunden.
    Leider ist es mir noch nicht gelungen einen sauberen Programmabbruch mit Strg-C zu programmieren. Das Problem scheinen die unabhängigen Programmteile zu sein. Im Moment wachsen mir deshalb allerdings keine grauen Haare. Auch das Beenden des Abbrandes ist noch nicht gelöst. Meistens drehe ich den Gebläsen den Saft ab und starte das Programm beim nächsten Abbrand neu. Das muss allerdings noch richtig gelöst werden. Da der Raspi auch den Heizungsmischer regelt läuft er immer.


    VG
    Ralf

  • Zur Steuerung der Lambda-Sonde verwende ich die Schaltung von Sebastian Knödler. Im Moment kommuniziert der Raspi per serielle Schnittstelle mit der Lambda Schaltung. Das will ich jedoch ändern und zukünftig die Analogsignale verwenden. Dadurch kann man sich die extra Schaltung sparen, die notwendig ist um die seriellen 5V Signale des Raspi auf 12V Signale für die Lambda Schaltung umzuwandeln.


    Moin Ralf,


    coole Sache, das :woohoo: Mach' dir noch ein GUI mit Python, dann kannst du deine SW auch starten, stoppen, ... Ich würde dazu einen zentralen Scheduler empfehlen, der die Unterprogramme zeitgesteuert aufruft.
    Dann noch eine Klasse für die Messwerte, eine für I/O, ...


    Den CJ125 würde ich weiterhin vom AVR auslesen lassen. Da kommt ja nicht nur der O2-Wert, sondern auch das Statusregister des CJ125 etc. Wenn du den AVR umgehst, musst du den CJ125 per SPI auslesen, sonst kommst du nicht an das Fehlerregister.
    Und, ganz wichtig: Vor dem Verbinden oder abziehen des UART IMMER stromlos schalten. Ich kenne einen, der sich dabei mal den AVR auf der Knödler-Platine geschossen hat :whistle:


    Viel Spaß noch,
    Gerrit

    HVS40E mit Martins 12-Loch-Düse
    AK3000
    Lambdaregelung mittels Arduino Mega 2560 (3,2" Touch-Display)
    LSU 4.2 mit Knödler-Interface
    Luftverteilung per Kulisse und Belimo
    4000l Puffer





    "Don't mess with idiots. They drag you down to their level and beat you with experience."


  • Hallo Gerrit,
    Danke für den Hinweis mit dem UART. Ich bin auch so einer, der im laufenden Betrieb umbaut, solange nicht die 230V Seite betroffen ist :blush:
    Das mit der GUI ist auch eine gute Idee. Das macht es viel einfacher die Parameter im Betrieb zu ändern. Bisher war die GUI nicht im Focus, doch ich werde wohl damit anfangen. Wie ich das mit dem Scheduler mache, muss ich mir noch überlegen. Vielleicht gibt es da schon Klassen, die hilfreich sind. Ich habe das Programm ohne zentralen Scheduler geschrieben, damit ich mich nach dem Aufruf der Klasse nicht mehr darum kümmern muss. Das hat aber durchaus auch Nachteile.


    VG
    Ralf

  • Die Software hat in den letzten Tagen ein paar kleine Änderungen bekommen. Die Gebläse werden jetzt vom Raspberry direkt per PWM angesteuert. Der Arduino hat im Moment nur noch den PT1000 auszulesen.
    Außerdem habe ich eine Rücknahme der Primärluft eingebaut falls O2 unter eine Schwelle fällt.
    Mit dem GUI habe ich auch angefangen, ist jedoch noch nicht im Hauptprogramm implementiert.


    Ich werde in längeren Abständen immer mal wieder den jeweils aktuellen Code hier einstellen. Falls jemand nicht warten kann, einfach kurze PN. Allerdings scheint das Interesse so groß nicht zu sein. Deshalb mülle ich auch das Forum nicht jede Woche mit dem Code voll.


    VG
    Ralf

  • Da mir mal wieder eine SD Karte im Raspi abgeraucht ist und ich mal wieder kein Image gespeichert hatte, musste ich die Raspi neu konfigurieren, damit z.B. UART und W1 wieder funktionieren.
    UART braucht das Programm um mit der Knödler Schaltung zu kommunizieren. W1 (oneWire) wird für die Thermoelemente benötigt.
    Damit ich beim nächsten Mal nicht wieder suchen muss, stelle ich hier mal gleich die Informationen rein.


    UART:
    $sudo nano /boot/cmdline.txt


    Entfernen des Befehls: console=ttyAMA0,115200 kgdboc=ttyAMA0,115200
    ---
    sudo nano /etc/inittab


    Zeile am Ende auskommentieren:
    T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100
    ---
    W1: Einfügen der folgenden Befehle in /etc/modules
    w1-gpio
    w1-therm
    ---
    sudo nano /boot/config.txt


    Anfügen von:
    dtoverlay=w1-gpio,gpiopin=4
    ---


    Ich habe inzwischen wieder einige Änderungen im Programm vorgenommen:
    - Die PWM Regelung habe ich wieder dem Arduino übergeben. Der Raspi hat leider nur einen Hardware PWM Pin (18). Alle anderen Pins können nur Software PWM. Trotz DMA ist Soft-PWM zu langsam um meine Gebläse mit Vollleistung laufen zu lassen. Außerdem scheint die Frequenz des Soft-PWM nicht sauber zu sein. Die Gebläse habe dadurch manchmal komische Sachen gemacht. Im Arduino klappt es besser. Evtl. umgehe ich in Zukunft die Nanpy Library und schreibe die Regelung direkt in den Arduino. Damit wäre es dann auch nicht so tragisch, wenn sich die serielle Verbindung mal aufhängt.


    Eine weitere Änderung ist die Verwendung des gpio Befehls von Gordon Henderson. Damit muss ich zwar etwas unschön einen Shell Befehl aufrufen um den GPIO Pin Status zu ändern, aber es hat den Vorteil, dass ich das Programm jetzt ohne root Rechte laufen lassen kann. Die GPIO Ansteuerung ist nicht Zeitkritisch, daher geht es auch mit dem Shell Befehl ohne Probleme.


    Mit dem GUI habe ich angefangen. O2 und AGT lassen sich jetzt per Schieberegler einstellen.


    VG
    Ralf

  • Das Problem mit den Verbindungsabbrüchen zwischen Raspi und Arduino scheint gelöst zu sein. Der Author der Library Nanpy hat sich bei mir gemeldet. Er wusste wohl schon von dem Problem und hat es, wie es aussieht, behoben. Ich habe gestern die neue Nanpy Firmware auf den Arduino aufgespielt und seither gab es keine Abbrüche mehr. Das Problem war Speicherfragmentierung im Arduino. Die Nanpy Firmware hatte malloc und free Befehle verwendet um dynamisch Speicher zu belegen und wieder frei zu geben. Dadurch wird der Arbeitsspeicher des Arduino aber so fragmentiert, dass irgendwann mal ein malloc fehl schlägt, weil im Speicher kein zusammenhängender Platz mehr groß genug ist. Offensichtlich hat dann auch meine Watchdog Funktion nichts mehr gebracht. Wenn der Arduino abstürzt kann er natürlich auch keinen Reset Befehl mehr ausführen. Die dynamische Speicherbelegung wurde in der Firmware jetzt durch statische Speicherbelegung ersetzt.
    Ich hatte mir schon alternativen ausgedacht. Eine Alternative war, einen Raspi GPIO Pin mit dem Reset Pin des Arduino zu verbinden und bei Fehlschlagen der Verbindung einen Hardwarereset vom Raspy aus auszuführen. Das wäre ein etwas unschöner Hack. Deshalb hatte ich mir schon vorgenommen, die ganze Kesselsteuerung in den Arduino zu programmieren. Gebläse und AGT Fühler hängen ohnehin schon am Arduino. Die Knödlerplatine könnte ich über den analogen 0-5V Ausgang auch locker an den Arduino anschließen. Die paar Programmzeilen würden in den Speicher passen. RLA und Heizungssteuerung sowie das Datenlogging könnte dann weiterhin der Raspi machen. Wenns mich reitet, mache ich das noch irgendwann, jetzt werde ich mich aber erst mal weiter auf das GUI konzentrieren.


    VG
    Ralf

  • Das letzte Update der Nanpy Library hat leider nichts gebracht, im Gegenteil. Ich hatte dadurch noch mehr Abstürze des Programms als vorher. Deshalb habe ich Nanpy jetzt komplett raus geschmissen. Sattdessen habe ich mein eigenes Arduino Progrämmchen geschrieben. Bisher läuft die Kiste ohne Abstürze. Mal sehen, ob es ein paar Tage stabil läuft.
    Ich werde mal wieder die aktuellen Versionen von GasyDruino und GasyPi posten. Das GUI ist noch unvollendet, aber daran werde ich in den nächsten Wochen noch arbeiten.


    GasyDruino:


    #include <SoftwareSerial.h> // We need this even if we're not using a SoftwareSerial object
    // Due to the way the Arduino IDE compiles
    #include <SerialCommand.h>
    #include <PWM.h>


    SerialCommand SCmd; // The SerialCommand object
    unsigned long interval=500; // the time we need to wait
    unsigned long previousMillis=0; // millis() returns an unsigned long.
    void setup()
    {
    Serial.begin(115200);
    InitTimersSafe();
    SetPinFrequencySafe(9, 10000);
    SetPinFrequencySafe(10, 10000);
    pwmWrite(9, 0);
    pwmWrite(10, 0);
    pinMode(2, INPUT);
    // Setup callbacks for SerialCommand commands
    SCmd.addCommand("B1",Blower1);
    SCmd.addCommand("B2",Blower2);
    }


    void loop()
    {
    SCmd.readSerial();
    // Nothing will change until millis() increments by interval
    if ((unsigned long)(millis() - previousMillis) >= interval)
    {
    previousMillis = millis();
    Serial.print(analogRead(0));
    Serial.print(";");
    Serial.println(digitalRead(2));
    }
    }



    void Blower1()
    {
    pwmWrite(9, process_command());
    }


    void Blower2()
    {
    pwmWrite(10, process_command());
    }


    unsigned int process_command()
    {
    int aNumber;
    char *arg;


    arg = SCmd.next();
    if (arg != NULL)
    {
    aNumber=atoi(arg);
    return(aNumber);
    }
    else {
    return(false);
    }
    }

  • GasyPi:


    #!/usr/bin/python


    import os.path
    import time
    import datetime
    import threading
    import serial
    import sys
    import os


    os.system('gpio mode 1 out') #Board 12
    os.system('gpio mode 14 out') #Board 23
    os.system('gpio mode 2 out') #Board 13
    os.system('gpio mode 0 out') #Board 11
    os.system('gpio mode 3 out') #Board 15
    os.system('gpio mode 4 out') #Board 16
    os.system('gpio mode 5 out') #Board 18
    os.system('gpio mode 6 out') #Board 22


    # ===========================================================================
    # Class LambdaControl
    # ===========================================================================
    from threading import Thread


    class LambdaControl:
    def __init__(self):
    self.ser = serial.Serial(port='/dev/ttyAMA0', baudrate=115200,parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=1)
    self.ser.flushInput()
    self.ser.readline() #Clean out buffer and discard first line
    self.lambdaProbeOff()
    Thread(target = self.readLambda).start()


    def readLambda(self):
    self.active = 1
    self.O2 = ""
    while self.active:
    line = self.ser.readline()
    temp_data = line.split(";")
    if len(temp_data) == 4:
    temp = float(temp_data[0])
    if int(temp_data[1]) != 160:
    self.O2 = ""
    else:
    self.O2 = str("{:2.1f}".format(temp/10))


    def lambdaProbeOn(self):
    self.ser.write("\rH\r")


    def lambdaProbeOff(self):
    self.ser.write("\rD\r")


    def getRestO2(self):
    return(self.O2)

    def __del__(self):
    self.lambdaProbeOff()
    self.active = 0
    # ===========================================================================
    # End of Class LambdaControl
    # ===========================================================================


    # ===========================================================================
    # Class ArduinoControl
    # ===========================================================================
    from threading import Thread


    class ArduinoControl:
    def __init__(self):
    self.switch = 0
    self.ser = serial.Serial(port='/dev/ttyACM0', baudrate=115200,parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=1)
    self.ser.flushInput()
    self.ser.readline() #Clean out buffer and discard first line
    Thread(target = self.readArduino).start()


    def readArduino(self):
    self.active = 1
    self.pt1000_temp = -999
    while self.active:
    line = self.ser.readline()
    temp_data = line.split(";")
    try:
    temp = float(temp_data[0])
    except:
    continue
    self.pt1000_temp = (temp/2) - 32
    self.switch = int(temp_data[1])


    def sendArduinoCommand(self, value):
    self.ser.write(value)


    def getTemp(self):
    return(self.pt1000_temp)


    def getResetSwitch(self):
    return(self.switch)

    def __del__(self):
    self.active = 0
    # ===========================================================================
    # End of Class ArduinoControl
    # ===========================================================================


    # ===========================================================================
    # Class BurnerControl
    # ===========================================================================
    import time


    class BurnerControl:
    def __init__(self, Primary_P, Primary_I, Primary_D, Primary_Timer, Secondary_P, Secondary_I, Secondary_D, Secondary_Timer, ArduinoClass, LambdaClass):
    self.startupTime = 120
    self.startupSpeed = 50
    self.heatupStep = 5
    self.maxPrimarySpeed = 80
    self.maxSecondarySpeed = 100
    self.targetTemp = 180
    self.targetLambda = 4
    self.lambdaClass = LambdaClass
    self.primarySpeed = 0
    self.secondarySpeed = 0
    self.pPID=PID(Primary_P, Primary_I, Primary_D, Integrator_max = 10, Integrator_min = -10)
    self.sPID=PID(Secondary_P, Secondary_I, Secondary_D, Integrator_max = 10, Integrator_min = -10)
    self.pPID.setPoint(self.targetTemp)
    self.sPID.setPoint(self.targetLambda)
    self.pTimer = Primary_Timer
    self.sTimer = Secondary_Timer
    self.ArduinoClass = ArduinoClass
    self.startupTemp = 100
    self.burnerStatus = 0
    self.counter = 0
    self.active = 1
    Thread(target = self.checkStartButton).start()
    Thread(target = self.modeScheduler).start()
    Thread(target = self.pidLambda).start()

    def checkStartButton(self):
    while self.active:
    if self.ArduinoClass.getResetSwitch():
    self.burnerStatus = 1
    self.counter = 0
    self.lambdaClass.lambdaProbeOn()
    time.sleep(0.5)


    def modeScheduler(self):
    while self.active:
    if self.burnerStatus == 0:
    self.waitToStart()
    if self.burnerStatus == 1:
    self.heatupBurner()
    if self.burnerStatus == 2:
    self.pidBurner()
    if self.burnerStatus == 3:
    self.O2byPrimary()
    time.sleep(self.pTimer)

    def waitToStart(self):
    if self.ArduinoClass.getTemp() > self.startupTemp:
    self.lambdaClass.lambdaProbeOn()
    self.setPrimarySpeed(self.startupSpeed)
    self.burnerStatus = 1 #Burner started
    self.counter = 0
    return
    self.stopPrimaryBlower()
    self.stopSecondaryBlower()
    self.lambdaClass.lambdaProbeOff()

    def heatupBurner(self):
    self.counter += 1
    if self.counter < 15:
    self.setPrimarySpeed(self.startupSpeed)
    return
    if (self.ArduinoClass.getTemp() > self.targetTemp - 5) or (self.counter > 180):
    self.burnerStatus = 2 #PID Mode
    return
    try:
    l = float(self.lambdaClass.getRestO2())
    except:
    l = self.targetLambda
    newSpeed = self.getPrimarySpeed()
    if l < 10:
    newSpeed = self.getPrimarySpeed() + 1
    if (l < self.targetLambda - 1) and (self.getSecondarySpeed() > 98):
    newSpeed = self.getPrimarySpeed() - 5
    if l > 15:
    self.burnerStatus = 3
    return
    self.setPrimarySpeed(newSpeed)

    def pidBurner(self):
    try:
    O2 = float(self.lambdaClass.getRestO2())
    except:
    O2 =0
    if O2 > self.targetLambda + 2:
    self.burnerStatus = 3 #Slow down mode
    return
    if O2 < self.targetLambda - 1 and self.getSecondarySpeed() > 98:
    newSpeed = self.getPrimarySpeed() - 10
    else:
    pid = self.pPID.update(self.ArduinoClass.getTemp())
    newSpeed = self.primarySpeed + pid
    self.setPrimarySpeed(newSpeed)


    def pidLambda(self):
    while self.active:
    if self.burnerStatus == 0:
    self.stopSecondaryBlower()
    else:
    try:
    O2 = float(self.lambdaClass.getRestO2())
    pid = self.sPID.update(O2)
    self.setSecondarySpeed(self.secondarySpeed + pid)
    except:
    self.setSecondarySpeed(40) #Emergency mode if Lambda is not giving numbers
    time.sleep(self.sTimer)


    def O2byPrimary(self):
    if self.getSecondarySpeed() > 15:
    self.burnerStatus = 2
    try:
    del myPID
    except:
    pass
    return
    try:
    O2 = float(self.lambdaClass.getRestO2())
    except:
    O2 = self.targetLambda + 5
    try:
    pid = myPID.sPID.update(O2)
    except:
    myPID=PID(1, 0.1, 0.5)
    myPID.setPoint(self.targetLambda)
    pid = myPID.update(O2)
    if self.ArduinoClass.getTemp() < 120:
    if self.ArduinoClass.getTemp() < 100:
    self.burnerStatus = 0
    self.stopPrimaryBlower() #Just shut the blower down when temp is less than 120. No mode change yet, otherwise the burner will start-up again
    return
    newSpeed = self.primarySpeed + pid
    if newSpeed < 25:
    newSpeed = 25
    self.setPrimarySpeed(newSpeed)


    def setPrimarySpeed(self, percent):
    if percent > self.maxPrimarySpeed:
    percent = self.maxPrimarySpeed
    if percent < 10:
    percent = 10
    speed = percent * 2.55
    self.ArduinoClass.sendArduinoCommand("B1 " + str(int(round(speed))) + "\r")
    self.primarySpeed = int(round(percent))


    def setSecondarySpeed(self, percent):
    if percent > self.maxSecondarySpeed:
    percent = self.maxSecondarySpeed
    if percent < 0:
    percent = 0
    speed = percent * 2.55
    self.ArduinoClass.sendArduinoCommand("B2 " + str(int(round(speed))) + "\r")
    self.secondarySpeed = int(round(percent))


    def stopPrimaryBlower(self):
    self.ArduinoClass.sendArduinoCommand("B1 0\r")
    self.primarySpeed = 0


    def stopSecondaryBlower(self):
    self.ArduinoClass.sendArduinoCommand("B2 0\r")
    self.secondarySpeed = 0

    def getPrimarySpeed(self):
    return(self.primarySpeed)


    def getSecondarySpeed(self):
    return(self.secondarySpeed)


    def getStatus(self):
    return(self.burnerStatus)


    def setTargetO2(self, value):
    self.targetLambda = value
    self.sPID.setPoint(self.targetLambda)


    def setTargetTemp(self, value):
    self.targetTemp = value
    self.pPID.setPoint(self.targetTemp)


    def setSecondaryP(self, value):
    self.sPID.setKp(value)


    def setSecondaryI(self, value):
    self.sPID.setKi(value)


    def setSecondaryD(self, value):
    self.sPID.setKd(value)


    def setSecondaryTimer(self, value):
    self.sTimer = value


    def __del__(self):
    self.active = 0
    # ===========================================================================
    # End of Class BurnerControl
    # ===========================================================================


    # ===========================================================================
    # Class OneWire
    # ===========================================================================
    from threading import Thread


    class OneWire:
    def __init__(self, file):
    self.file = file
    self.temperature = -999
    Thread(target = self.readTemp).start()


    def readTemp(self):
    self.active = 1
    while self.active:
    text = ""
    try:
    myfile = open(self.file)
    text = myfile.read()
    myfile.close()
    except:
    text = "NO"
    if text.find("NO") == -1:
    temp_data = text.split()[-1]
    temp = float(temp_data[2:])
    if temp > 0:
    self.temperature = temp / 1000
    time.sleep(0.5)


    def getTemp(self):
    return(self.temperature)


    def __del__(self):
    self.active = 0
    # ===========================================================================
    # End of Class OneWire
    # ===========================================================================


    # ===========================================================================
    # Class PID
    # ===========================================================================


    #The recipe gives simple implementation of a Discrete Proportional-Integral-Derivative (PID) controller. PID controller gives output value for error between desired reference input and measurement feedback to minimize error value.
    #More information: http://en.wikipedia.org/wiki/PID_controller
    #
    #cnr437@gmail.com
    #
    ####### Example #########
    #
    #p=PID(3.0,0.4,1.2)
    #p.setPoint(5.0)
    #while True:
    # pid = p.update(measurement_value)
    #
    #



    class PID:
    """
    Discrete PID control
    """


    def __init__(self, P=2.0, I=0.0, D=1.0, Derivator=0, Integrator=0, Integrator_max=500, Integrator_min=-500):


    self.Kp=P
    self.Ki=I
    self.Kd=D
    self.Derivator=Derivator
    self.Integrator=Integrator
    self.Integrator_max=Integrator_max
    self.Integrator_min=Integrator_min


    self.set_point=0.0
    self.error=0.0


    def update(self,current_value):
    """
    Calculate PID output value for given reference input and feedback
    """


    self.error = self.set_point - current_value


    self.P_value = self.Kp * self.error
    self.D_value = self.Kd * ( self.error - self.Derivator)
    self.Derivator = self.error


    self.Integrator = self.Integrator + self.error


    if self.Integrator > self.Integrator_max:
    self.Integrator = self.Integrator_max
    elif self.Integrator < self.Integrator_min:
    self.Integrator = self.Integrator_min


    self.I_value = self.Integrator * self.Ki


    PID = self.P_value + self.I_value + self.D_value
    return PID


    def setPoint(self,set_point):
    """
    Initilize the setpoint of PID
    """
    self.set_point = set_point
    self.Integrator=0
    self.Derivator=0


    def setIntegrator(self, Integrator):
    self.Integrator = Integrator


    def setDerivator(self, Derivator):
    self.Derivator = Derivator


    def setKp(self,P):
    self.Kp=P


    def setKi(self,I):
    self.Ki=I


    def setKd(self,D):
    self.Kd=D


    def getPoint(self):
    return self.set_point


    def getError(self):
    return self.error


    def getIntegrator(self):
    return self.Integrator


    def getDerivator(self):
    return self.Derivator


    # ===========================================================================
    # End of Class PID
    # ===========================================================================


    # ===========================================================================
    # Class MixerControl
    # ===========================================================================


    class MixerControl:
    def __init__(self, mytc, Target, P, I, D, timer, GPIO_warm, GPIO_cold):
    self.myTempClass = mytc
    self.TargetTemp = Target
    self.p=PID(P, I, D, Integrator_max = 25, Integrator_min = -25)
    self.timer = timer
    self.pin_warm = GPIO_warm
    self.pin_cold = GPIO_cold
    self.p.setPoint(Target)
    self.mixerControl()


    def mixerCold(self, secs):
    os.system('gpio write {gpio} 0'.format(gpio=str(self.pin_warm)))
    os.system('gpio write {gpio} 1'.format(gpio=str(self.pin_cold)))
    self.timerThread = threading.Timer(secs, self.mixerOff)
    self.timerThread.start()


    def mixerWarm(self, secs):
    os.system('gpio write {gpio} 0'.format(gpio=str(self.pin_cold)))
    os.system('gpio write {gpio} 1'.format(gpio=str(self.pin_warm)))
    self.timerThread = threading.Timer(secs, self.mixerOff)
    self.timerThread.start()


    def mixerOff(self):
    os.system('gpio write {gpio} 0'.format(gpio=str(self.pin_cold)))
    os.system('gpio write {gpio} 0'.format(gpio=str(self.pin_warm)))


    def setTemp(self, value):
    self.p.setPoint(value)
    self.TargetTemp = value

    def mixerControl(self):
    actTemp = self.myTempClass.getTemp()
    pid = self.p.update(actTemp)
    if pid > 0:
    if pid > 30:
    pid = 30
    self.mixerWarm(pid)
    else:
    if pid < -30:
    pid = -30
    self.mixerCold(pid * -1)
    self.thr = threading.Timer(self.timer, self.mixerControl)
    self.thr.start()


    def __del__(self):
    self.timerThread.cancel()
    self.thr.cancel()


    # ===========================================================================
    # End of Class MixerControl
    # ===========================================================================


    # ===========================================================================
    # Class RLA
    # ===========================================================================
    class RLAControl:
    def __init__(self, mixerClass, tempInClass, tempOutClass, burnerClass):
    self.mixerClass = mixerClass
    self.tempInClass = tempInClass
    self.tempOutClass = tempOutClass
    self.burnerClass = burnerClass
    Thread(target = self.controlRLA).start()

    def controlRLA(self):
    self.active = 1
    while self.active:
    if self.tempOutClass.getTemp() > 70 or self.tempInClass.getTemp() > 70:
    os.system('gpio write 14 1') #RLA Pump on
    else:
    os.system('gpio write 14 0') #RLA Pump off
    time.sleep(10)


    def setRLATemp(self, value):
    self.mixerClass.setTemp(value)


    def getRLAOutTemp(self):
    return(self.tempOutClass.getTemp())


    def __del__(self):
    self.active = 0


    # ===========================================================================
    # End of Class RLA
    # ===========================================================================


    # ===========================================================================
    # Class HeatingCurve
    # ===========================================================================
    class HeatingCurve:
    def __init__(self, mixerClass, outsideTempClass):
    self.mixerClass = mixerClass
    self.outsideTempClass = outsideTempClass
    self.baseTemp = 55 # at 0 Celsius outside
    Thread(target = self.controlHeat).start()

    def controlHeat(self):
    self.active = 1
    while self.active:
    self.mixerClass.setTemp(self.baseTemp - self.outsideTempClass.getTemp())
    print self.baseTemp - self.outsideTempClass.getTemp()
    time.sleep(600)


    def __del__(self):
    self.active = 0


    # ===========================================================================
    # End of Class HeatingCurve
    # ===========================================================================


    # ===========================================================================
    # Class NewDatalogger
    # ===========================================================================
    import gdata.spreadsheet.service
    import gdata.docs.client


    class NewDatalogger:
    def __init__(self):
    self.openSpreadsheet()


    def insertRow(self, values):
    tday = datetime.date.weekday(datetime.date.today())
    if(tday != self.lastDay):
    self.openSpreadsheet()
    row = { "a": values[0], "b": values[1], "c": values[2], "d": values[3], "e": values[4], "f": values[5], "g": values[6], "h": values[7], "i": values[8], "j": values[9], "k": values[10], "l": values[11], "m": values[12], "n": values[13], "o": values[14], "p": values[15] }
    self.gs.InsertRow(row, self.spreadsheet_key, self.worksheetID)


    def openSpreadsheet(self):
    self.email = 'USERNAME@gmail.com'
    self.password = 'PASSWORD'
    self.source = 'GasPi'
    self.worksheetID = 'od6'
    foldername = 'GasPi'
    tday = datetime.date.today()
    self.lastDay = datetime.date.weekday(tday)
    filename = 'GasPi Log ' + str(tday)
    gc = gdata.docs.client.DocsClient()
    gc.ClientLogin(self.email, self.password, self.source)


    try: #Try open existing folder
    q = gdata.docs.client.DocsQuery(title=foldername, title_exact='true', show_collections='true')
    folder = gc.GetResources(q=q).entry[0]
    contents = gc.GetResources(uri=folder.content.src)
    except: #Create new folder
    folder = gdata.docs.data.Resource('folder', foldername)
    contents = gc.CreateResource(folder)
    q = gdata.docs.client.DocsQuery(title=foldername, title_exact='true', show_collections='true')
    folder = gc.GetResources(q=q).entry[0]
    contents = gc.GetResources(uri=folder.content.src)


    try: #Try open existing spreadsheet
    q = gdata.docs.client.DocsQuery(title=filename, title_exact='true', show_collections='true')
    document = gc.GetResources(q=q).entry[0]
    self.spreadsheet_key = document.GetId().split("%3A")[1]
    self.ssLogin()
    except:
    try: #Copy Template
    q = gdata.docs.client.DocsQuery(title='GasPi Log Template', title_exact='true', show_collections='true')
    document = gc.GetResources(q=q).entry[0]
    copy = gc.CopyResource(document, filename)
    gc.MoveResource(copy, folder, False) #For some reason it does not really move it, just a symlink
    self.spreadsheet_key = copy.GetId().split("%3A")[1]
    self.ssLogin()
    except: #Create new spreadsheet
    document = gdata.docs.data.Resource('spreadsheet', filename)
    document = gc.CreateResource(document, collection=folder)
    self.spreadsheet_key = document.GetId().split("%3A")[1]
    self.ssLogin()
    headers = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p"]
    for i, header in enumerate(headers):
    entry = self.gs.UpdateCell(row=1, col=i+1, inputValue=header, key=self.spreadsheet_key, wksht_id=self.worksheetID)
    row = { "a": "Time", "b": "BasementTemperature", "c": "Oxygen", "d": "ExhaustTemp", "e": "GasifierWaterExitTemp", "f": "OutsideTemp", "g": "MixerForward", "h": "MixerReturn", "i": "PrimaryBlowerSpeed", "j": "SecondaryBlowerSpeed", "k": "Tank1Top", "l": "Tank1Middle", "m": "Tank1Bottom", "n": "Tank2Top", "o": "Tank2Middle", "p": "Tank2Bottom"}
    self.gs.InsertRow(row, self.spreadsheet_key, self.worksheetID)


    def ssLogin(self):
    self.gs = gdata.spreadsheet.service.SpreadsheetsService()
    self.gs.email = self.email
    self.gs.password = self.password
    self.gs.source = self.source
    self.gs.ProgrammaticLogin()
    # ===========================================================================
    # End of NewDatalogger
    # ===========================================================================


    # ===========================================================================
    # Class Alarm
    # ===========================================================================
    import datetime
    import threading


    class Alarm:
    def __init__(self):
    self.timerThread = threading.Timer(1, self.checkAlarm)
    self.timerThread.start()
    self.startWaterHeater(3000)


    def checkAlarm(self):
    now = datetime.datetime.now().time()
    if (now.hour == 5 and now.minute == 0) or (now.hour == 15 and now.minute == 30):
    self.startWaterHeater(3600)
    else:
    os.system('gpio write 1 0')
    self.timerThread = threading.Timer(60, self.checkAlarm)
    self.timerThread.start()


    def startWaterHeater(self, time):
    try:
    self.timerThread.cancel()
    except:
    pass
    os.system('gpio write 1 1')
    self.timerThread = threading.Timer(time, self.checkAlarm)
    self.timerThread.start()


    # ===========================================================================
    # End of Class Alarm
    # ===========================================================================


    # ===========================================================================
    # Class UpdateDatabase
    # ===========================================================================
    class UpdateDatabase:
    def __init__(self, nd, lc, ac, bentryt, bexitt, tank1top, tank1middle, tank1bottom, tank2top, tank2middle, tank2bottom, mixforward, mixreturn, outside, bc):
    self.datalogger = nd
    self.O2 = lc
    self.pt1000 = ac
    self.boilerEntry = bentryt
    self.boilerExit = bexitt
    self.buffer1t = tank1top
    self.buffer1m = tank1middle
    self.buffer1b = tank1bottom
    self.buffer2t = tank2top
    self.buffer2m = tank2middle
    self.buffer2b = tank2bottom
    self.mixerforward = mixforward
    self.mixerback = mixreturn
    self.outside = outside
    self.burner = bc
    Thread(target = self.updateNow).start()


    def updateNow(self):
    self.active = 1
    while self.active:
    values = []
    values.append(str(datetime.datetime.time(datetime.datetime.now()))[:8])
    values.append(str("{:3.1f}".format(self.outside.getTemp())).replace(".", ","))
    values.append(self.O2.getRestO2().replace(".", ","))
    values.append(str(self.pt1000.getTemp()).replace(".", ","))
    values.append(str("{:3.1f}".format(self.boilerEntry.getTemp())).replace(".", ","))
    values.append(str("{:3.1f}".format(self.boilerExit.getTemp())).replace(".", ","))
    values.append(str("{:3.1f}".format(self.mixerforward.getTemp())).replace(".", ","))
    values.append(str("{:3.1f}".format(self.mixerback.getTemp())).replace(".", ","))
    values.append(str(self.burner.getPrimarySpeed()))
    values.append(str(self.burner.getSecondarySpeed()))
    values.append(str("{:3.1f}".format(self.buffer1t.getTemp())).replace(".", ","))
    values.append(str("{:3.1f}".format(self.buffer1m.getTemp())).replace(".", ","))
    values.append(str("{:3.1f}".format(self.buffer1b.getTemp())).replace(".", ","))
    values.append(str("{:3.1f}".format(self.buffer2t.getTemp())).replace(".", ","))
    values.append(str("{:3.1f}".format(self.buffer2m.getTemp())).replace(".", ","))
    values.append(str("{:3.1f}".format(self.buffer2b.getTemp())).replace(".", ","))
    try:
    self.datalogger.insertRow(values)
    except:
    pass
    time.sleep(8.9)
    # ===========================================================================
    # End of Class UpdateDatabase
    # ===========================================================================


    # ===========================================================================
    # Class GUI
    # ===========================================================================
    from Tkinter import *
    import ttk
    from random import randint



    class GUI:
    def onO2Scale(self, value):
    self.O2.set(round(float(value), 1))
    self.burnerClass.setTargetO2(float(value))

    def onExhaustScale(self, value):
    self.exhaustTemp.set(round(float(value), 0))
    self.burnerClass.setTargetTemp(float(value))


    def onO2P(self, value):
    self.O2P.set(round(float(value), 1))
    self.burnerClass.setSecondaryP(float(value))


    def onO2I(self, value):
    self.O2I.set(round(float(value), 1))
    self.burnerClass.setSecondaryI(float(value))


    def onO2D(self, value):
    self.O2D.set(round(float(value), 1))
    self.burnerClass.setSecondaryD(float(value))


    def onO2T(self, value):
    self.O2T.set(round(float(value), 1))
    self.burnerClass.setSecondaryTimer(float(value))


    def onRLAScale(self, value):
    self.RLATemp.set(round(float(value), 0))
    self.rla.setRLATemp(float(value))


    def onHWScale(self, value):
    self.hWTemp.set(round(float(value), 0))

    def updateData(self):
    self.root.after(1000, self.updateData)
    self.actO2.set(self.lc.getRestO2())
    self.actET.set(self.ac.getTemp())
    self.actEW.set(round(self.rla.getRLAOutTemp(), 1))
    self.actBlower1.set(self.burnerClass.getPrimarySpeed())
    self.actBlower2.set(self.burnerClass.getSecondarySpeed())


    def __init__(self, bc, rla, alarm, lc, ac):
    self.burnerClass = bc
    self.rla = rla
    self.alarm = alarm
    self.lc = lc
    self.ac = ac
    root = Tk()
    self.root = root
    root.title("GasiPy Heating System Control")
    root.geometry("480x272")
    self.mainframe = ttk.Notebook(root, padding="6 6 12 12")
    self.mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
    f0 = ttk.Frame(self.mainframe, padding="6 6 12 12")
    f01 = ttk.Labelframe(f0, text='Sensors', padding="6 6 12 12")
    f1 = ttk.Frame(self.mainframe, padding="6 6 12 12")
    f11 = ttk.Labelframe(f1, text='Secondary Blower Settings', padding="6 6 12 12")
    f11.grid(column=0, row=0, sticky=(N, W, E, S))
    f12 = ttk.Labelframe(f1, text='Primary Blower Settings', padding="6 6 12 12")
    f12.grid(column=0, row=1, sticky=(N, W, E, S))
    f2 = ttk.Frame(self.mainframe)
    f3 = ttk.Frame(self.mainframe)
    f4 = ttk.Frame(self.mainframe)
    f5 = ttk.Frame(self.mainframe)
    f6 = ttk.Frame(self.mainframe)
    tab0 = self.mainframe.add(f0, text='Readings')
    tab4 = self.mainframe.add(f4, text='Heat')
    tab5 = self.mainframe.add(f5, text='Waterheater')
    tab1 = self.mainframe.add(f1, text='Blower I')
    tab2 = self.mainframe.add(f2, text='Blower II')
    tab3 = self.mainframe.add(f3, text='RLA')
    tab6 = self.mainframe.add(f6, text='Oiler')


    self.mainframe.columnconfigure(0, weight=1)
    self.mainframe.rowconfigure(0, weight=1)
    self.O2 = StringVar()
    self.O2.set("4.0")
    self.exhaustTemp = StringVar()
    self.exhaustTemp.set("150.0")
    self.RLATemp = StringVar()
    self.RLATemp.set("72.0")
    self.hWTemp = StringVar()
    self.hWTemp.set("45.0")
    self.O2P = StringVar()
    self.O2P.set("2.0")
    self.O2I = StringVar()
    self.O2I.set("0.0")
    self.O2D = StringVar()
    self.O2D.set("4.0")
    self.O2T = StringVar()
    self.O2T.set("11.0")
    self.actO2 = StringVar()
    self.actET = StringVar()
    self.actEW = StringVar()
    self.actBlower1 = StringVar()
    self.actBlower2 = StringVar()

    ttk.Label(f0, text="Rest O2 in Flue Gas:", font = 'helvetica 20').grid(column=1, row=1, sticky = W, padx=5, pady=5)
    ttk.Label(f0, text="Flue Gas Temperature:", font = 'helvetica 20').grid(column=1, row=2, sticky = W, padx=5, pady=5)
    ttk.Label(f0, text="Exit Water Temp.:", font = 'helvetica 20').grid(column=1, row=3, sticky = W, padx=5, pady=5)
    ttk.Label(f0, text="Prim. Blower Speed:", font = 'helvetica 20').grid(column=1, row=4, sticky = W, padx=5, pady=5)
    ttk.Label(f0, text="Sec. Blower Speed:", font = 'helvetica 20').grid(column=1, row=5, sticky = W, padx=5, pady=5)
    ttk.Label(f0, textvariable = self.actO2, foreground = 'red', font = 'helvetica 20').grid(column=2, row=1, sticky = W, padx=5, pady=5)
    ttk.Label(f0, text = "%", font = 'helvetica 20').grid(column=3, row=1, sticky = W, padx=5, pady=5)
    ttk.Label(f0, textvariable = self.actET, foreground = 'red', font = 'helvetica 20').grid(column=2, row=2, sticky = W, padx=5, pady=5)
    ttk.Label(f0, text = "deg C", font = 'helvetica 20').grid(column=3, row=2, sticky = W, padx=5, pady=5)
    ttk.Label(f0, textvariable = self.actEW, foreground = 'red', font = 'helvetica 20').grid(column=2, row=3, sticky = W, padx=5, pady=5)
    ttk.Label(f0, text = "deg C", font = 'helvetica 20').grid(column=3, row=3, sticky = W, padx=5, pady=5)
    ttk.Label(f0, textvariable = self.actBlower1, foreground = 'red', font = 'helvetica 20').grid(column=2, row=4, sticky = W, padx=5, pady=5)
    ttk.Label(f0, text = "%", font = 'helvetica 20').grid(column=3, row=4, sticky = W, padx=5, pady=5)
    ttk.Label(f0, textvariable = self.actBlower2, foreground = 'red', font = 'helvetica 20').grid(column=2, row=5, sticky = W, padx=5, pady=5)
    ttk.Label(f0, text = "%", font = 'helvetica 20').grid(column=3, row=5, sticky = W, padx=5, pady=5)

    self.updateData() #Start Update Timer


    myrow = 1
    ttk.Label(f11, text="Target O2 in %").grid(column=1, row=myrow, sticky = W, padx=5, pady=5)
    ttk.Scale(f11, orient=HORIZONTAL, length = 200, from_= 2.0, to = 10.0, command = self.onO2Scale, variable = self.O2).grid(column = 2, row = myrow, sticky=W, padx=5, pady=5)
    ttk.Label(f11, textvariable=self.O2).grid(column=3, row=1, sticky=W, padx=5, pady=5)
    myrow +=1
    ttk.Label(f11, text="P value for PID Controller").grid(column=1, row=myrow, sticky=W, padx=5, pady=5)
    ttk.Scale(f11, orient=HORIZONTAL, length=200, from_= 0, to = 20, command = self.onO2P, variable = self.O2P).grid(column = 2, row = myrow, sticky=W, padx=5, pady=5)
    ttk.Label(f11, textvariable=self.O2P).grid(column=3, row=myrow, sticky=W, padx=5, pady=5)
    myrow +=1
    ttk.Label(f11, text="I value for PID Controller").grid(column=1, row=myrow, sticky=W, padx=5, pady=5)
    ttk.Scale(f11, orient=HORIZONTAL, length=200, from_= 0, to = 20, command = self.onO2I, variable = self.O2I).grid(column = 2, row = myrow, sticky=W, padx=5, pady=5)
    ttk.Label(f11, textvariable=self.O2I).grid(column=3, row=myrow, sticky=W, padx=5, pady=5)
    myrow +=1
    ttk.Label(f11, text="D value for PID Controller").grid(column=1, row=myrow, sticky=W, padx=5, pady=5)
    ttk.Scale(f11, orient=HORIZONTAL, length=200, from_= 0, to = 20, command = self.onO2D, variable = self.O2D).grid(column = 2, row = myrow, sticky=W, padx=5, pady=5)
    ttk.Label(f11, textvariable=self.O2D).grid(column=3, row=myrow, sticky=W, padx=5, pady=5)
    myrow +=1
    ttk.Label(f11, text="Timer for PID Controller in sec").grid(column=1, row=myrow, sticky=W, padx=5, pady=5)
    ttk.Scale(f11, orient=HORIZONTAL, length=200, from_= 0.1, to = 20, command = self.onO2T, variable = self.O2T).grid(column = 2, row = myrow, sticky=W, padx=5, pady=5)
    ttk.Label(f11, textvariable=self.O2T).grid(column=3, row=myrow, sticky=W, padx=5, pady=5)
    myrow +=1
    ttk.Label(f12, text="Target Exhaust Temp in deg C").grid(column=1, row=myrow, sticky=W, padx=5, pady=5)
    ttk.Scale(f12, orient=HORIZONTAL, length=200, from_=120, to=250, command = self.onExhaustScale, variable = self.exhaustTemp).grid(column = 2, row = myrow, sticky=W, padx=5, pady=5)
    ttk.Label(f12, textvariable=self.exhaustTemp).grid(column=3, row=myrow, sticky=W, padx=5, pady=5)
    myrow +=1
    ttk.Label(f2, text="Target RLA Temperature in degrees C").grid(column=1, row=myrow, sticky=W, padx=5, pady=5)
    ttk.Scale(f2, orient=HORIZONTAL, length=200, from_=60, to=85, command = self.onRLAScale, variable = self.RLATemp).grid(column = 2, row = myrow, sticky=W, padx=5, pady=5)
    ttk.Label(f2, textvariable=self.RLATemp).grid(column=3, row=myrow, sticky=W, padx=5, pady=5)
    myrow +=1
    ttk.Label(f2, text="Target Heating Water Temperature in degrees C").grid(column=1, row=myrow, sticky=W, padx=5, pady=5)
    ttk.Scale(f2, orient=HORIZONTAL, length=200, from_=35, to=85, command = self.onHWScale, variable = self.hWTemp).grid(column = 2, row = myrow, sticky=W, padx=5, pady=5)
    ttk.Label(f2, textvariable=self.hWTemp).grid(column=3, row=myrow, sticky=W, padx=5, pady=5)


    root.mainloop()


    # ===========================================================================
    # End of Class GUI
    # ===========================================================================


    # ===========================================================================
    # Main Code
    # ===========================================================================
    bexitt = OneWire("/sys/bus/w1/devices/28-000004abfb55/w1_slave") # Boilerwater exit temp
    bentryt = OneWire("/sys/bus/w1/devices/28-000005e5ccc8/w1_slave") # Boilerwater entry temp
    mixreturn = OneWire("/sys/bus/w1/devices/28-000005143eb4/w1_slave") # Mixer heating water forward temp
    mixforward = OneWire("/sys/bus/w1/devices/28-000005f403dc/w1_slave") #Mixer heating return temp
    tank1top = OneWire("/sys/bus/w1/devices/28-000005f1ff79/w1_slave") #Buffer Tank 1 top
    tank1middle = OneWire("/sys/bus/w1/devices/28-000005f2fe6f/w1_slave") #Buffer Tank 1 middle
    tank1bottom = OneWire("/sys/bus/w1/devices/28-000005f2f56c/w1_slave") #Buffer Tank 1 bottom
    tank2top = OneWire("/sys/bus/w1/devices/28-000005f3d4e2/w1_slave") #Buffer Tank 2 top
    tank2middle = OneWire("/sys/bus/w1/devices/28-000005f375bc/w1_slave") #Buffer Tank 2 middle
    tank2bottom = OneWire("/sys/bus/w1/devices/28-000005f318a9/w1_slave") #Buffer Tank 2 bottom
    outside = OneWire("/sys/bus/w1/devices/28-000004bff14b/w1_slave")


    ric = MixerControl(bentryt, 72, 0.7, 0.02, 5, 60, 6, 5)
    roomHeat = MixerControl(mixforward, 50, 3, 0.2, 4, 20, 3, 4)
    lc = LambdaControl() #This will start reading the lambda board every 0.5s
    ac = ArduinoControl()
    nd = NewDatalogger()
    bc = BurnerControl(0.4, 0.1, 3, 10, 4, 0.1, 1, 15, ac, lc)
    rla = RLAControl(ric, bentryt, bexitt, bc)
    alarm = Alarm()
    HeatingCurve(roomHeat, outside)


    ud = UpdateDatabase(nd, lc, ac, bentryt, bexitt, tank1top, tank1middle, tank1bottom, tank2top, tank2middle, tank2bottom, mixforward, mixreturn, outside, bc)


    GUI(bc, rla, alarm, lc, ac)
    raise SystemExit
    lc.__del__()
    bc.__del__()
    ric.__del__()
    pt.__del__()
    del bexitt
    del bentryt
    del mixreturn
    del mixforward
    del nd
    sys.exit()

  • Heute habe ich eine Idee umgesetzt auf die mich Stefan (Hobbele) gebracht hat.
    Dazu habe ich auf dem Raspi einen VNC Server installiert. Der Clou bei der Sache ist, dass man mit der Konfiguration wie sie unter dem folgenden Link beschrieben ist den Raspi Desktop auf dem Internet Browser sehen kann. Link: VNC remote desktop via web browser


    Mit der dazugehörigen GUI von GasyPi kann ich den Kessel jetzt sowohl im Keller am Monitor oder am Laptop im Wohnzimmer einstellen. Ein paar Screenshots dazu:



    VG
    Ralf

  • Prima!
    Da könnte ich ja beim segeln von der Adria aus den HV beobachten.
    Im Sommer macht das aber Solar, aber ich könnte.
    Ich brauch erst noch den Monitor, darf den großen im Wohnzimmer nicht nehmen.
    Grüße Martin

    Soli 25 E 2000 l Puffer Solar 8,9m2 Röhren
    Plattentaush. Friwa eigenbau
    Flamtronik 2Gebl. Konr. Dimmer.
    HKS u. EigenDüse Gr. Brennk. Eigenbau
    CO Messung

  • Prima!
    Da könnte ich ja beim segeln von der Adria aus den HV beobachten.
    Im Sommer macht das aber Solar, aber ich könnte.
    Ich brauch erst noch den Monitor, darf den großen im Wohnzimmer nicht nehmen.
    Grüße Martin


    Der Witz ist, Du brauchst nur den Monitor am PC. Der Raspi braucht gar keinen. Der Raspi Bildschirm wird dann so zu sagen virtuell auf dem PC Bildschirm angezeigt.


    VG
    Ralf


  • Der Witz ist, Du brauchst nur den Monitor am PC. Der Raspi braucht gar keinen. Der Raspi Bildschirm wird dann so zu sagen virtuell auf dem PC Bildschirm angezeigt.


    VG
    Ralf


    Aber wenn noch nichts auf dem Raspi ist, muß doch erst was rein, muß doch erst mal was sehen was ich mache.
    Brauch doc erst mal eine Anzeige, später kann ich doch damit auch fern sehen ohne Fernglas.
    Auf dem Cat hab ich den kleinen für Fußball und Olympiade, das auch mit kühlem Bier und ab und zu einem
    Außenbordskameraden in der Pfanne.
    Grüße Martin


    Grüße Martin

    Soli 25 E 2000 l Puffer Solar 8,9m2 Röhren
    Plattentaush. Friwa eigenbau
    Flamtronik 2Gebl. Konr. Dimmer.
    HKS u. EigenDüse Gr. Brennk. Eigenbau
    CO Messung

  • Aber wenn noch nichts auf dem Raspi ist, muß doch erst was rein, muß doch erst mal was sehen was ich mache.
    Brauch doc erst mal eine Anzeige, später kann ich doch damit auch fern sehen ohne Fernglas.
    Auf dem Cat hab ich den kleinen für Fußball und Olympiade, das auch mit kühlem Bier und ab und zu einem
    Außenbordskameraden in der Pfanne.
    Grüße Martin


    Grüße Martin


    Man kann den Raspi ganz ohne Bildschirm und Tastatur konfigurieren. Schau mal nach 'raspberry ohne Maus und Tastatur in Betrieb nehmen'.
    Natürlich brauchst Du für Fußball und Olympiade einen Monitor, da kann ich jetzt leider nicht helfen ;) Da musste halt der Chefin klar machen, dass der Raspi ganz wichtig ist um heutzutage fern zu sehen. Wenn die Werbung kommt kannst Du dann ja auf Holzvergaser oder Solar umschalten :laugh:


    VG
    Ralf

  • De kleinen LCD hab ich ja, aber mit Antenne oder VGA die drei Stecker wiß, gelb, rot.
    komm da aber nicht rei, oder mit der Karte nicht ok, drei mal aufgesetzt frißt er aber nicht, weiß nicht ob er hoch fährt.
    Grüße Martin

    Soli 25 E 2000 l Puffer Solar 8,9m2 Röhren
    Plattentaush. Friwa eigenbau
    Flamtronik 2Gebl. Konr. Dimmer.
    HKS u. EigenDüse Gr. Brennk. Eigenbau
    CO Messung

  • De kleinen LCD hab ich ja, aber mit Antenne oder VGA die drei Stecker wiß, gelb, rot.
    komm da aber nicht rei, oder mit der Karte nicht ok, drei mal aufgesetzt frißt er aber nicht, weiß nicht ob er hoch fährt.
    Grüße Martin


    Du brauchst nur das gelbe Kabel. Wenn Du das an die gelbe Buchse am Raspi und an die gelbe Buchse vom Monitor verbindest, sollte er sich eigentlich zeigen. Der kleine LCD braucht aber natürlich auch Betriebsspannung (12V?).
    Wenn das nicht geht, versuche mal den Raspi mit einem Netzwerkkabel an Deinen Internetrouter (Fritzbox?)anzuschließen (ohne Monitor). Wenn Du Dir dann auf dem Router die vorhandenen Geräte im Netzwerk ansiehst, sollte sich da ein Raspberry angemeldet haben. Wenn das geklappt hat, notiere Dir die IP Adresse des Raspberry. Dann gehst Du zu Deinem Computer, wenn Du Ubuntu hast, kannst Du einfach in der Konsole den Befehl:
    ssh pi@10.0.0.10 -X (die jeweilige IP Adresse aus der Fritzbox anstatt 10.0.0.10 ) eingeben. Das Passwort ist in der Grundeinstellung raspberry.
    Wenn Du Windows hast, musst Du Dir erst das Programm Putty.exe runterladen und starten. Das Programm kenne ich aber nicht auswendig, da kann ich im Moment nicht viel dazu schreiben. Auf jeden Fall kannst Du damit das Gleiche machen wie der Befehl ssh in Linux.
    Wenn das alles nicht geht, stimmt wahrscheinlich etwas mit dem Image auf der Karte nicht. Allerdings sollte er sich dann trotzdem auf dem Monitor zeigen. Es könnte aber auch sein, dass Dein Netzteil für den Raspi nicht stark genug ist. Die 500mA Teile schwächeln da schon mal.
    Wäre doch gelacht, wenn Du so ne Himbeere nicht zum laufen bekommst, nachdem Du einen HV bezwungen hast :)


    VG
    Ralf

Jetzt mitmachen!

Sie haben noch kein Benutzerkonto auf unserer Seite? Registrieren Sie sich kostenlos und nehmen Sie an unserer Community teil!