scotty101
Posts: 2795
Joined: Fri Jun 08, 2012 6:03 pm

Re: Python, Tkinter and GPIO

Thu Apr 07, 2016 2:58 pm

Yep. keep those lines.
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

Co Termors
Posts: 12
Joined: Fri Mar 25, 2016 10:58 am

Re: Python, Tkinter and GPIO

Thu Apr 07, 2016 3:37 pm

Hi Scotty101,

OK I put those three lines back in, and I removed a line inadvertently left in, calling on the now removed callback.

There are no error messages now, but there is a new problem.
Initially the four relays on the left side of the menu work just fine. After closing the switch to input GPIO 4 it triggers GPIO 17 (Relay 1) just fine at any time and shuts off after a second or two.

However when I trigger GPIO 17 (Relay 1) via the left side of the menu the software shuts it off instantly again. When I activate relays 2,3 and 4 they work just fine. Any suggestion?

PS if anyone is interested we got the Red break-out card and the relay card for about $10 together. Let me know and I'll post the website.


Code: Select all

##https://www.raspberrypi.org/forums/viewtopic.php?f=32&t=41639
## Scotty101 - reads all three versions of RPi
## this version customized by for Relay Board: 4 relays 4 inputs
##============= denotes beginning of a major new section ============================= 
#--------------- denotes end of major section ----------------------------------------
        #------- denotes  divider between subsections --------------------------------



import sys

if(sys.version_info[0]<3):
    from Tkinter import *
else:
    from tkinter import *
    
import RPi.GPIO as pi
import math
#import tkSimpleDialog
pi.setwarnings(False)

pi.setmode(pi.BCM)

pi.setup(4, pi.IN, pull_up_down=pi.PUD_DOWN)
pi.setup(17, pi.OUT, initial=pi.LOW)

##====================================================================================
class LED(Frame):
    """A Tkinter LED Widget.
    a = LED(root,10)
    a.set(True)
    current_state = a.get()"""
    OFF_STATE = 0
    ON_STATE = 1
    #---------------------------------------------------------------------------------
    def __init__(self,master,size=10,**kw):
        self.size = size
        Frame.__init__(self,master,width=size,height=size)
        self.configure(**kw)
        self.state = LED.OFF_STATE
        self.c = Canvas(self,width=self['width'],height=self['height'])
        self.c.grid()
        self.led = self._drawcircle((self.size/2)+1,(self.size/2)+1,(self.size-1)/2)
    #---------------------------------------------------------------------------------
    def _drawcircle(self,x,y,rad):
        """Draws the circle initially"""
        color="red"
        return self.c.create_oval(x-rad,y-rad,x+rad,y+rad,width=rad/5,fill=color,outline='black')
    #---------------------------------------------------------------------------------
    def _change_color(self):
        """Updates the LED colour"""
        if self.state == LED.ON_STATE:
            color="green"
        else:
            color="red"
        self.c.itemconfig(self.led, fill=color)
    #---------------------------------------------------------------------------------
    def set(self,state):
        """Set the state of the LED to be True or False"""
        self.state = state
        self._change_color()
    #---------------------------------------------------------------------------------
    def get(self):
        """Returns the current state of the LED"""
        return self.state
    #-------------------------------------------------------------------------------------
##====================================================================================
## Future Functionality
##class gpioEdit(tkSimpleDialog.Dialog):
##    """Dialog to be expanded to support advanced gpio features like
##       - Pull Up / Pull Down Resistor Config
##       - Debounce"""
##    def __init__(self, master,gpio):
##        top = self.top = Toplevel(master)
##        if gpio.isInput():
##            title = "Edit Input: %s" %(str(gpio.name))
##        else:
##            title = "Edit Output: %s" %(str(gpio.name))
##        l = Label(top,text=title)
##        b = Button(top, text="Submit", command=self.submit)
##
##        l.grid(row=0)
##        b.grid(row=1)
##
##    def submit(self):
##        print("Submitted")
##        self.top.destroy()
#-------------------------------------------------------------------------------------

##====================================================================================
class GPIO(Frame):
    """Each GPIO class draws a Tkinter frame containing:
    - A Label to show the GPIO Port Name
    - A data direction spin box to select pin as input or output
    - A checkbox to set an output pin on or off
    - An LED widget to show the pin's current state
    - A Label to indicate the GPIOs current function"""
    gpio_modes = ("Passive","Input","Output")
    
    #---------------------------------------------------------------------------------
    def __init__(self,parent,pin=0,name=None,bk='blue',fg='white',**kw):
        self.pin = pin
        if name == None:
            self.name = "GPIO %02d" % (self.pin)
            
            #  Line above is replaced by the "if' section below
            #  to replace 'GPIO #' by 'Relay' or 'Input'
            if self.pin == 17 : self.name = "Relay 1 "
            if self.pin == 18 : self.name = "Relay 2 "
            if self.pin == 27 : self.name = "Relay 3 "
            if self.pin == 22 : self.name = "Relay 4 "
            
            if self.pin == 23 : self.name = "Input 1 "
            if self.pin == 24 : self.name = "Input 2 "
            if self.pin == 25 : self.name = "Input 3 "
            if self.pin == 04 : self.name = "Input 4 "
        Frame.__init__(self,parent,width=150,height=20,relief=SUNKEN,bd=1,padx=5,pady=5)
        ##Future capability
        ##self.bind('<Double-Button-1>', lambda e, s=self: self._configurePin(e.y))
        self.parent = parent
        self.configure(**kw)
        self.state = False
        self.cmdState = IntVar()
        self.Label = Label(self,text=self.name)
        self.mode_sel = Spinbox(self,values=self.gpio_modes,wrap=True,command=self.setMode)
        self.set_state = Checkbutton(self,text="High/Low",variable=self.cmdState,command=self.toggleCmdState)
        self.led = LED(self,20)
        self.Label.grid(column=0,row=0)
        self.mode_sel.grid(column=1,row=0)
        self.set_state.grid(column=2,row=0)
        self.current_mode = StringVar()
        self.led.grid(column=3,row=0)

        self.set_state.config(state=DISABLED)
        function = self.getPinFunctionName()
        if function not in ['Input','Output']:
            self.mode_sel.delete(0,'end')
            self.mode_sel.insert(0,function)
            self.mode_sel['state'] = DISABLED
    #---------------------------------------------------------------------------------
           
##    def _configurePin(self, y):
##        """Future capability to setup pull up/down"""
##        new = gpioEdit(self.parent,self)

    #---------------------------------------------------------------------------------
    def isInput(self):
        """Returns True if the current pin is an input"""
        return (self.mode_sel.get() == "Input")

    #---------------------------------------------------------------------------------
    def setMode(self):
        """Sets the GPIO port to be either an input or output
            Depending on the value in the spinbox"""
        if (self.mode_sel.get() == "Input"):
            self.set_state.config(state=DISABLED)
            pi.setup(self.pin,pi.IN)
        elif (self.mode_sel.get() == "Passive"):
            self.set_state.config(state=DISABLED)
            pi.cleanup(self.pin)
        else:
            self.set_state.config(state=NORMAL)
            pi.setup(self.pin,pi.OUT)
        self.updateInput()

    #---------------------------------------------------------------------------------
    def getPinFunctionName(self):
        pin = self.pin
        functions = {pi.IN:'Input',
                     pi.OUT:'Output',
                     pi.I2C:'I2C',
                     pi.SPI:'SPI',
                     pi.HARD_PWM:'HARD_PWM',
                     pi.SERIAL:'Serial',
                     pi.UNKNOWN:'Unknown'}                     
        return functions[pi.gpio_function(pin)]
    #---------------------------------------------------------------------------------
   

#-------------------------------------------------------------------------------------
## Future Functionality
##    def setPullUp(self,pullup):
##        """Defines the GPIO as having a pull up resistor so the input
##        state is inverted when read
##        setPullUp(True) - Pin is pulled up
##        setPullUP(False) - Pin is not pulled up"""
##        self.pullup = pullup

#-------------------------------------------------------------------------------------
    def toggleCmdState(self):
        """Reads the current state of the checkbox, updates LED widget
        and sets the gpio port state."""
        self.state = self.cmdState.get()
        self.updateLED()
        self.updatePin()

#-------------------------------------------------------------------------------------
    def updatePin(self):
        """Sets the GPIO port state to the current state"""
        pi.output(self.pin,self.state)
        
#-------------------------------------------------------------------------------------
    def updateLED(self):
        """Refreshes the LED widget depending on the current state"""
        self.led.set(self.state)

#-------------------------------------------------------------------------------------
    def updateInput(self):
        """Updates the current state if the pin is an input and sets the LED"""
        if self.isInput():
            state = pi.input(self.pin)
            self.state = state
            self.updateLED()
        
#-------------------------------------------------------------------------------------
  
##====================================================================================
class App(Frame):
    def __init__(self,parent=None, **kw):
        Frame.__init__(self,parent,**kw)
        self.parent = parent
        pi.setmode(pi.BCM)
        self.ports = []
        ## Get the RPI Hardware dependant list of GPIO
        gpio = self.getRPIVersionGPIO()
        for num,(p,r,c) in enumerate(gpio):
            self.ports.append(GPIO(self,pin=p))
            self.ports[-1].grid(row=r,column=c)
        self.update()

    #---------------------------------------------------------------------------------
    def onClose(self):
        """This is used to run the Rpi.GPIO cleanup() method to return pins to be an input
        and then destory the app and its parent."""
        try:
            pi.cleanup()
        except RuntimeWarning as e:
            print(e)
        self.destroy()
        self.parent.destroy()

    #---------------------------------------------------------------------------------
    def readStates(self):
        """Cycles through the assigned ports and updates them based on the GPIO input"""
        for port in self.ports:
            port.updateInput()
                    
    #---------------------------------------------------------------------------------
        """def update(self):
        @Runs every 100ms to update the state of the GPIO inputs
        self.readStates()
        self._timer = self.after(100,self.update)
        """
    #---------------------------------------------------------------------------------
    def update(self):
        """Runs every 100ms to update the state of the GPIO inputs"""
        self.readStates()
        if pi.input(4):
            pi.output(17,1)
        else:
            pi.output(17,0)
        self._timer = self.after(100,self.update)
   #---------------------------------------------------------------------------------
    def getRPIVersionGPIO(self):
        """Returns the GPIO hardware config for different Pi versions
           Currently supports layout 1 and 3"""
        gpio1 = ((0,0,0),
                (1,1,0),
                (4,2,0),
                (17,3,0),
                (21,4,0),
                (22,5,0),
                (10,6,0),
                (9,7,0),
                (11,8,0),
                (14,0,1),
                (15,1,1),
                (18,2,1),
                (23,3,1),
                (24,4,1),
                (25,5,1),
                (8,6,1),
                (7,7,1))
                #==== old set up ======================
                #        gpio2 = ((2,0,0),  #Relay 1
                #                (3,1,0),
                #                (4,2,0),
                #                (17,3,0),   #GPIO2
                #                (27,4,0),   #Relay3
                #                (22,5,0),   #Relay4
                #                (10,6,0),
                #                (9,7,0),
                #                (11,8,0),
                #                (14,0,1),
                #                (15,1,1),
                #                (18,2,1),   #Relay 2
                #                (23,3,1),
                #                (24,4,1),
                #                (25,5,1),
                #                (8,6,1),
                #                (7,7,1))
                #===== new set up ======================
        gpio2 = ((17,0,0),  #Relay 1
                (18,1,0),   #Relay 2
                (27,2,0),   #Relay 3
                (22,3,0),   #Relay 4
                (23,0,1),   #input 1
                (24,1,1),   #input 2
                (25,2,1),   #input 3
                (04,3,1))    #input 4
                #to remove a menu item, delete line, remove 
                # the comma in the line above and replace the
                # the comma by a rt ) and place # in front of
                # removed menu item - as above and below
                #(07,4,1))    #GPIO 7
                #=====  end of new set up ==============
              ##-------------------------------------------------------------------
        gpio3 = ((17,0,0),  #Relay 1
                (18,1,0),   #Relay 2
                (27,2,0),   #Relay 3
                (22,3,0),   #Relay 4
                (23,0,1),   #input 1
                (24,1,1),   #input 2
                (25,2,1),   #input 3
                (04,3,1))    #input 4
                #to remove a menu item, delete line, remove 
                # the comma in the line above and replace the
                # the comma by a rt ) and place # in front of
                # removed menu item - as above and below
                #(07,4,1))    #GPIO 7
                #=====  end of new set up ==============
        """gpio3 = ((2,0,0),
                (3,1,0),
                (4,2,0),
                (17,3,0),
                (27,4,0),
                (22,5,0),
                (10,6,0),
                (9,7,0),
                (11,8,0),
                (5,9,0),
                (6,10,0),
                (13,11,0),
                (19,12,0),
                (26,13,0),
                (14,0,1), 
                (15,1,1),
                (18,2,1),
                (23,3,1),
                (24,4,1),
                (25,5,1),
                (8,6,1),
                (7,7,1),
                (12,8,1),
                (16,9,1),
                (20,10,1),
                (21,11,1))"""
      
##-------------------------------------------------------------------
        if pi.RPI_REVISION == 3:
            gpio = gpio3
            self.parent.title('Raspberry Pi GPIO - A+/B+/2B+  V8.1  with trigger')
        elif pi.RPI_REVISION == 2:
            #Change this when I know the pins on RPi GPIO Version 2
            gpio = gpio2
            self.parent.title('Raspberry Pi GPIO - A/B Rev2')
        elif pi.RPI_REVISION == 1:
            self.parent.title('Raspberry Pi GPIO - A/B')
            gpio = gpio1
        else:
            self.parent.title('Raspberry Pi GPIO - Unknown Version')
            ##Assume same config as A+/B+/2B+
            gpio = gpio3
        return gpio
#-------------------------------------------------------------------------------------

##========Main Menu Setup ============================================================
def main():
    root = Tk()
    root.title("Raspberry Pi GPIO  ")
    a = App(root)
    a.grid()
    """When the window is closed, run the onClose function."""
    root.protocol("WM_DELETE_WINDOW",a.onClose)
    root.resizable(False,False)
    root.mainloop()
   

if __name__ == '__main__':
    main()
#-------------------------------------------------------------------------------------

scotty101
Posts: 2795
Joined: Fri Jun 08, 2012 6:03 pm

Re: Python, Tkinter and GPIO

Thu Apr 07, 2016 3:48 pm

That makes sense.

You haven't added relays 2, 3 and 4 to the code aside from the GUI buttons.

Basically you need to think of how you want the system to perform if the GPIO button is pressed or if the GUI button is pressed

My code does the following
Reads GPIO if they are inputs and sets the LED on or off
or
Turns on or off a GPIO if it is set as an output based on the checkbox.

What you are asking is to read a GPIO and set another output based on BOTH the button/checkbox and another GPIO pin.

What should the code do if the GPIO input is true and then the button is pressed? Should it turn it off? Or keep it on?

If i had the time to write the application, I would start from the basics and remove a lot of the 'generic' code that I put in to deal with all of the GPIO pins.

Also, the switches/buttons that you have connected to the GPIO, do they stay closed when you press them or are they momentarily pressed?
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

Co Termors
Posts: 12
Joined: Fri Mar 25, 2016 10:58 am

Re: Python, Tkinter and GPIO

Thu Apr 07, 2016 5:37 pm

Harry has been going through your program line by line to see how it works for the last week or so, and we might soon be able to write our own version that does only what we need it to do. Whatever we've tried so far in Tkinter regarding the IO trigger left some sort of thread working in the background, often even after the main was shut down, or produced errors that we couldn't resolve.

We're at the point now where we can do pretty well what we need to do in Python, without too many difficulties. However when

We will need in all likelihood need to use momentary inputs as well as switches. Sometimes will have to have complete override. We will also need to control stepper motor to control track switches. We want to divide the track into sectors and prevent a train from entering a sector which already has a train in it

Apart from your excellent work and Llyod Seaton's modifications there is very little recent work that we've been able to find that we've been able to get working. A lot of the imports, suggested downloads etc are no longer functioning, or available where they said they'd be. All kinds of suggestions out there, none of which worked, or at least I couldn't figure out how to get them to work.

The call-back routine so far has been the least restrictive - while it produced errors on the output side, the errors were not terminal, and allowed us to continue functioning.

I love the changes you suggested, and we'll keep plugging away to see if we can rid of the problems.

We really appreciate all the work you've done and the help you've provided, Scotty101

Co Termors

RDS
Posts: 623
Joined: Tue Oct 06, 2015 8:17 am
Location: Lancashire, UK

Re: Python, Tkinter and GPIO

Thu Nov 16, 2017 11:34 pm

@scotty101
I know it is over 18 months since this thread was in use but I have just come across it, whilst trying to make a start on converting a program with just a text based output, to one that uses tkinter.
My program monitors the output from my Solar panels and has been running for almost 2 years now. I have a Phototransistor mounted on the Solar Panel Meter, in front of an LED that flashes once for each Watt generated. My program works very well but I have always wanted to turn it into one that would (for example) display a meter, graduated 0-4 Kw. My program does calculate a figure that hopefully could be displayed by the tkinter generated meter.
As the phototransistor is connected to the GPIO pins, my first step will be to see if your program detects the change in the GPO pin. I am sure that I am a long way from achieving what I want but hopefully your listing will provide the start I need.

scotty101
Posts: 2795
Joined: Fri Jun 08, 2012 6:03 pm

Re: Python, Tkinter and GPIO

Fri Nov 17, 2017 10:20 am

If you share your existing program, I may be able to tell you how to wrap a GUI around it.

I certainly wouldn't consider my existing tkinter gpio program for your purpose, it only periodically reads from the GPIO ports rather than using interrupts/events to watch for any changes. If you increase the update rate, you might be lucky and catch some of the LED 'blinks' but you'll probably miss a few.
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

RDS
Posts: 623
Joined: Tue Oct 06, 2015 8:17 am
Location: Lancashire, UK

Re: Python, Tkinter and GPIO

Fri Nov 17, 2017 7:49 pm

@scotty101
Thank you very much. I would be grateful for any help you can provide, as I have completely failed so far myself.
My existing code is shown below.
It is complete except that I have replaced the email address and my file names with asterisks.
The program as written, works very well and sends me an email when the email threshold value (or multiples of it) has been reached for each day. It also provides a continuous output to a Python window of the amount generated each day.

The import os and xdotool lines are to ensure that the window created, is resized so that it fits well on my Android phone for remote access using VNC. Again this works well.

Code: Select all

# RDS Program to determine if light is falling on Photo Transistor
# Using TEPT5600 PhotoTransistor


import RPi.GPIO as GPIO
from datetime import  *
from time import time
import smtplib
from email.mime.text import MIMEText

import os
os.system("xdotool getactivewindow windowmove 15 75 windowsize 420 645")
# was 20 50 and 400 670
# The above 2 lines were given to me on the RPi Forum
# when i asked about resizing a window.  They work well
# and xdotool can do many more things.

GPIO.setmode(GPIO.BCM)
GPIO.setup(10, GPIO.IN)

L1=""
L2=""
L3=""

L4=""
tempstr = ""
tempint = 0

detect = 0
detct = 0
kwatt = ""
kilowatt = 0
wcount = 0
mcount = 0
cnt = 0
timenow = time()
detected = 0
lastpulse = 1
starttime = 0
averagee = 0
shortest = 999
email_threshold = 1500
csv_threshold = 100
lpulse = ""
subjectt = "Subject of message sent to gmail"
messagee = "A variable for a message sent to gmail"
timeunit = ""
kwhr = 0


def send_email(subjectt,messagee):
    USERNAME = "******.******@gmail.com"
    PASSWORD = "***************" 
    MAILTO  = "******.************@gmail.com"

    #msg = MIMEText('This is a test of an email from Raspberry Pi')
    msg = MIMEText(messagee)
    msg['Subject'] = subjectt
    msg['From'] = USERNAME
    msg['To'] = MAILTO

    server = smtplib.SMTP('smtp.gmail.com:587')
    server.ehlo_or_helo_if_needed()
    server.starttls()
    server.ehlo_or_helo_if_needed()
    server.login(USERNAME,PASSWORD)
    server.sendmail(USERNAME, MAILTO, msg.as_string())
    server.quit()



# print (int(timenow))   #This prints the integer date since Epoch (1/1/1970)

daynumber = datetime.now().strftime( '%j')
minutenumber = datetime.now().strftime( '%M')
dayandtime = datetime.now().strftime('%a %H:%M:%S')
dateandtime = datetime.now().strftime('%Y%m%d %H:%M:%S')

# Use stored value of 'detect' if program is restarted during a day
file = open('Desktop/Last_Detect.csv', 'r')
restartvalue = int(file.read ()) #added on 26/10/17
# detect = int(file.read ()) #remed on 26/10/17
detect = restartvalue
file.close()
print ('Starting at ',end="")
print (detect)

while True:
    input_value = GPIO.input(10)
        
    # Check if Day number has changed to reset counter
    if daynumber != datetime.now().strftime( '%j'):
     detect = 1
     
    if input_value == False:
        if detect > 0:
         today = datetime.now()
         fullday = today.strftime( '%A')
         shortday = today.strftime( '%a')
         dayandtime = datetime.now().strftime('%a %H:%M:%S')
         dateandtime = datetime.now().strftime('%Y%m%d %H:%M:%S')
         timee = datetime.now().strftime('%H:%M:%S')
                  
         if detect == 1:
           starttime = int(time())
         
         if detect >=2: 
             timenow = time()
             lastpulse = timenow - detected
          
             lpulse = '{:0.2f}'.format(lastpulse)

             if detect % email_threshold == 0: # threshold defined in variables
              send_email(str(detect) + " units today","Current Rate " + str(kwhr) + "kw/hr" + '\n' + "Last Rate" + '\n' + "Previous Rate")
              print ("email sent")
              averagee = '{:0.2f}'.format((timenow - starttime)/(detect-1))
         kwatt = '{:0.3f}'.format(detect/1000)
         

         # Prepare Data to send to LCD
         L1 = (fullday + ": " + kwatt + "kw").center (20," ")
         if lastpulse > 60:
             lpulse = (str(int(lastpulse//60))) + "min " + (str(int(lastpulse%60))) + "sec"
             L2 = (" Rate: " + lpulse).center (18," ")
         else:
             L2 = (" Rate: " + lpulse + "secs").center (18," ")
         L3 = ""
         kwhr = ('{:0.3f}'.format(3.600/(lastpulse)))  
         L3 = ("Last: "+ str(kwhr)+"kw/hr").center (20," ")
         L4 = ("email at " + (str(email_threshold)) + " units").center (20," ")
         

         # Send Output to Screen
         print (detect,end="")
         print ('  ',end="")
         print (dayandtime,end="")
         if detect != restartvalue: # added 26/10/17
          print(L2,str(kwhr)+" kw") # indented on 26/10/17
         

         # Send Output to file (This works well)
         if detect < 11 or detect % csv_threshold == 0: # threshold defined in variables
           datestring = datetime.strftime(datetime.now(), '%Y-%m-%d')
           file = open('******/******/******** '+ datestring +'.csv', 'a+')
           file.write ('\n')           
           file.write (str(detect))
           file.write (',')
           file.write (str(dateandtime))
           file.write (',')
           file.write (str(kwhr))
           file.close()
           
           #Also, send output to monthly summary csv file
           datestring = datetime.strftime(datetime.now(), '%Y-%m')
           file = open('*******/*********/******** '+ datestring +'.csv', 'a+')
           if detect == 1:
              file.write ('\n')
              file.write(datetime.now().strftime('%d'))
           file.write (',')
           file.write (str(timee))
           file.close()

         # Send the last 'detect' figure to a separate file
         file = open('Desktop/Last_Detect.csv', 'w')
         file.write (str(detect))
         file.close()


         # Send Output to LCD (Disabled at 25/3/2017)                   
         #lcd.disp(0,0,L1)
         #lcd.disp(0,1,L2)
         #lcd.disp(0,2,L3)
         #lcd.disp(0,3,L4)
         
        else:
         print ("Program started")
         
        detect = detect + 1
        daynumber = datetime.now().strftime( '%j')
        detected = (timenow)

        
                
        while input_value == False:
            input_value = GPIO.input(10)

RDS
Posts: 623
Joined: Tue Oct 06, 2015 8:17 am
Location: Lancashire, UK

Re: Python, Tkinter and GPIO

Sun Nov 19, 2017 1:44 pm

@scotty101
Your monitor program works extremely well at detecting the change of state of my GPIO10 and it mimics the LED on the Solar Panel perfectly.
So far, I have reduced the update time down to 10ms, made the 'ON' state colour light grey and the borders light grey in order to just see what looks like a flashing LED. (I don't think your background colour is light grey but it is fairly close).
I hope it is OK to play around with your program but it is an excellent starting point for me. Thank you.

RDS
Posts: 623
Joined: Tue Oct 06, 2015 8:17 am
Location: Lancashire, UK

Re: Python, Tkinter and GPIO

Sun Nov 19, 2017 6:38 pm

@scotty101
That didn't go well!
I was successful in changing colours and I like the way I can mimic the LED of the panel but your program is far in advance of anything I have done so far.
I would be grateful for any help you can give as suggested a few days ago,

RDS
Posts: 623
Joined: Tue Oct 06, 2015 8:17 am
Location: Lancashire, UK

Re: Python, Tkinter and GPIO

Tue Dec 05, 2017 7:42 pm

@scotty101
A few days ago we had more sunny weather than we have had for a few weeks and this was the first time I had been able to run the program with that level of sunshine. I noticed that the LED blinking was missed quite often by your monitoring program (like you suggested it probably would).

It is still impressive though the way it almost mimics the LED on the meter panel but some form of interrupt will be required.

scotty101
Posts: 2795
Joined: Fri Jun 08, 2012 6:03 pm

Re: Python, Tkinter and GPIO

Wed Dec 06, 2017 10:19 am

I don't have a Pi handy to test this on so it comes with very little guarantee.

I've created a simple tkinter gui with an LED widget and a counter. When the GPIO pin detects a falling edge the LED widget should turn on and the counter will be incremented. On the rising edge the LED widget is turned off.
I don't fully understand the logic of your code but hopefully this example will give you the basics to combine your code with a GUI.

Code: Select all

from tkinter import *

import Rpi.GPIO as GPIO

class LED(Frame):
    """A Tkinter LED Widget.

    a = LED(root,10)
    a.set(True)
    current_state = a.get()"""
    OFF_STATE = 0
    ON_STATE = 1
    
    def __init__(self,master,size=10,**kw):
        self.size = size
        Frame.__init__(self,master,width=size,height=size)
        self.configure(**kw)
        self.state = LED.OFF_STATE
        self.c = Canvas(self,width=self['width'],height=self['height'])
        self.c.grid()
        self.led = self._drawcircle((self.size/2)+1,(self.size/2)+1,(self.size-1)/2)
    def _drawcircle(self,x,y,rad):
        """Draws the circle initially"""
        color="red"
        return self.c.create_oval(x-rad,y-rad,x+rad,y+rad,width=rad/5,fill=color,outline='black')
    def _change_color(self):
        """Updates the LED colour"""
        if self.state == LED.ON_STATE:
            color="green"
        else:
            color="red"
        self.c.itemconfig(self.led, fill=color)
    def set(self,state):
        """Set the state of the LED to be True or False"""
        self.state = state
        self._change_color()
    def get(self):
        """Returns the current state of the LED"""
        return self.state

class App(Frame):
    def __init__(self,parent=None, **kw):
        self.pin = kw.pop('pin')
        Frame.__init__(self,parent,**kw)
        self.parent = parent
        #Counter is used to display the number of times the GPIO pin
        #detects a falling edge
        self.counter = IntVar()
        self.counter.set(0)

        #Create some simple widgets to display data
        Label(self,text="LED Interrupt Test").grid()
        self.led = LED(self,size=20)
        self.led.grid()
        Label(self,text="Blink Counter:").grid()
        Label(self,textvar=self.counter).grid()

        #Configure GPIO and Interrupts
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.pin,GPIO.IN)
        GPIO.add_event_detect(self.pin, GPIO.FALLING, callback=self.led_on)
        GPIO.add_event_detect(self.pin, GPIO.RISING, callback=self.led_off)

    #Function called when GPIO pin falls
    #turn the gui led on and increment counter
    def led_on(self):
        self.led.set(True)
        count = self.counter.get()
        count += 1
        self.counter.set(count)

    #Function called when GPIO pin rises
    #turn the gui led off
    def led_off(self):
        self.led.set(False)
        
    def onClose(self):
        GPIO.cleanup()
        self.destroy()
        self.parent.destroy()
        

def main():
    root = Tk()
    a = App(root,pin=17)
    a.grid()
    """When the window is closed, run the onClose function."""
    root.protocol("WM_DELETE_WINDOW",a.onClose)
    root.mainloop()
   

if __name__ == '__main__':
    main()

Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

RDS
Posts: 623
Joined: Tue Oct 06, 2015 8:17 am
Location: Lancashire, UK

Re: Python, Tkinter and GPIO

Wed Dec 06, 2017 11:07 pm

@scotty101
Thank you very much.
Hopefully it will be slightly sunny tomorrow so that I can try it properly!

I had to change the 2nd line to be RPi, rather than Rpi and also I am getting an error regarding the the fact that a conflicting edge detection is already enabled for this GPIO. At the moment I have just added the # symbol to the affected lines so that they do not cause the error and this has enabled the program to run and the tkinter window is displayed.

scotty101
Posts: 2795
Joined: Fri Jun 08, 2012 6:03 pm

Re: Python, Tkinter and GPIO

Thu Dec 07, 2017 10:03 am

If having multiple event detection on a single pin doesn't work try what is suggested at this link
http://raspi.tv/2014/rpi-gpio-update-an ... ling-edges
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

RDS
Posts: 623
Joined: Tue Oct 06, 2015 8:17 am
Location: Lancashire, UK

Re: Python, Tkinter and GPIO

Thu Dec 07, 2017 11:36 pm

@scotty101
Thanks, I have replaced the following 2 lines:

Code: Select all

GPIO.add_event_detect(self.pin, GPIO.FALLING, callback=self.led_on)
GPIO.add_event_detect(self.pin, GPIO.RISING, callback=self.led_off)
with the single line:

Code: Select all

GPIO.add_event_detect(self.pin, GPIO.BOTH, callback=self.led_off)
but I am unsure what to do about the callback statement later in the line or will it be correct to add a new function similar to that in the linked program to cover both the led_on and led_off options.
Last edited by RDS on Sun Dec 10, 2017 10:37 am, edited 1 time in total.

scotty101
Posts: 2795
Joined: Fri Jun 08, 2012 6:03 pm

Re: Python, Tkinter and GPIO

Fri Dec 08, 2017 9:52 am

The article I shared has the following lines of code

Code: Select all

def my_callback(channel):  
    if GPIO.input(25):     # if port 25 == 1  
        print "Rising edge detected on 25"  
    else:                  # if port 25 != 1  
        print "Falling edge detected on 25" 
This example uses a single callback for both the rising and falling edges.

The code that I provided could be modified in a similar way, the code currently in led_on could go in to one part of the if-else and led_off should go in to the other.
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

RDS
Posts: 623
Joined: Tue Oct 06, 2015 8:17 am
Location: Lancashire, UK

Re: Python, Tkinter and GPIO

Fri Dec 08, 2017 11:02 pm

@scotty101
Thank you for that answer because I am pleased that it follows what I was trying to say in my last post.

I had a number of errors when I first ran the program and I am working through them slowly and I have spent many hours on it today. I haven't yet managed to get it to detect a pulse but I hope I am getting closer!

I have inserted a lot of print statements in the program, such as position 1, position 2, position 3 etc, so that I can try to see the order in which the program executes. I have had no previous experience with classes but I think I am making progress.

I really appreciate the help you are giving me.

RDS
Posts: 623
Joined: Tue Oct 06, 2015 8:17 am
Location: Lancashire, UK

Re: Python, Tkinter and GPIO

Sun Dec 10, 2017 10:36 am

@scotty101
I am getting an error that I cannot clear:

Code: Select all

TypeError: led_on() takes 1 positional argument but 2 were given
I have made a few changes to your program, mainly to put check points in so that I could see the path the program is taking and have some means of checking which parts of the program I have altered.

Code: Select all

from tkinter import *

import RPi.GPIO as GPIO # RDS changed Rpi to RPi
# RDS Pin used is GPIO 10

#Define a threaded callback function to run in another thread when events are detected
channel = 10
def my_callback(channel):
        print("Position 1")
        if GPIO.input(10):     # if port 10 == 1  # was 25 RDS changed to 10
            print ("Rising edge detected on 10")
            self.led.set(True) # RDS added this line here
            count = self.counter.get() # RDS added this line here
            count += 1 # RDS added this line here
            self.counter.set(count) # RDS added this line here
        else:                  # if port 10 != 1  
            print ("Falling edge detected on 10")

class LED(Frame):
    # A Tkinter LED Widget.
    # a = LED(root,10)
    # a.set(True)
    # current_state = a.get()
    
    OFF_STATE = 0
    ON_STATE = 1
    
    print("Position 1a")
    def __init__(self,master,size=10,**kw):
        print("Position 2")
        self.size = size
        Frame.__init__(self,master,width=size,height=size)
        self.configure(**kw)
        self.state = LED.OFF_STATE
        self.c = Canvas(self,width=self['width'],height=self['height'])
        self.c.grid()
        self.led = self._drawcircle((self.size/2)+1,(self.size/2)+1,(self.size-1)/2)
    def _drawcircle(self,x,y,rad):
        print("Position 3")
        
        #Draws the circle initially
        color="red"
        return self.c.create_oval(x-rad,y-rad,x+rad,y+rad,width=rad/5,fill=color,outline='light grey')
    def _change_color(self):
        print("Position 4")
        
        #Updates the LED colour
        if self.state == LED.ON_STATE:
            color="green"
        else:
            color="red"
        self.c.itemconfig(self.led, fill=color)
    def set(self,state):
        print("Position 5")
        
        #Set the state of the LED to be True or False
        self.state = state
        self._change_color()
    def get(self):
        # Returns the current state of the LED
        return self.state
 
class App(Frame):
    print("Position 6")
    def __init__(self,parent=None, **kw):
        print("Position 6a")
        self.pin = kw.pop('pin')
        Frame.__init__(self,parent,**kw)
        self.parent = parent
        
        #Counter is used to display the number of times the GPIO pin
        #detects a falling edge
        self.counter = IntVar()
        self.counter.set(0)

        #Create some simple widgets to display data
        Label(self,text="LED Interrupt Test").grid()
        self.led = LED(self,size=20) # Sets the size of the LED
        self.led.grid()
        Label(self,text="Blink Counter:").grid()
        Label(self,textvar=self.counter).grid()
        #Label(self,text="Blink Counter2:").grid() # RDS just a test to add an extra line

        #Configure GPIO and Interrupts
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.pin,GPIO.IN)
        print("Position 6b")
        GPIO.add_event_detect(self.pin, GPIO.FALLING, callback=self.led_on) # added by RDS
        #GPIO.add_event_detect(self.pin, GPIO.RISING, callback=self.led_off) # added by RDS
        #GPIO.add_event_detect(self.pin, GPIO.BOTH, callback=my_callback) # added by RDS
        print("Position 6c")
        
    #Function called when GPIO pin falls
    #turn the gui led on and increment counter
    def led_on(self):
        print("Position 7")
        self.led.set(True)
        count = self.counter.get()
        count += 1
        self.counter.set(count)

    #Function called when GPIO pin rises
    #turn the gui led off
    def led_off(self):
        print("Position 8")
        self.led.set(False)

    def onClose(self):
        print("Position 10")
        GPIO.cleanup()
        self.destroy()
        self.parent.destroy()
        

def main():
    print("Position 11")
    root = Tk()
    print("Position 11a")
    a = App(root,pin=10) # RDS changed this to 10, it was pin=17
    print("Position 11b")
    a.grid()
    print("Position 11c")
    
    # When the window is closed, run the onClose function.
    root.protocol("WM_DELETE_WINDOW",a.onClose)
    root.mainloop()
   

if __name__ == '__main__':
    main()
Although the program counter does not yet count up, the error message is repeated every time the LED on the panel comes on so it is being detected.

Is there a simple fix to the positional arguments error, because I think if it get past that, the counter will work.

scotty101
Posts: 2795
Joined: Fri Jun 08, 2012 6:03 pm

Re: Python, Tkinter and GPIO

Mon Dec 11, 2017 9:47 am

Change
def led_on(self):
To
def led_on(self,channel):

Also you aren't turning the led widget off in the callback
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

RDS
Posts: 623
Joined: Tue Oct 06, 2015 8:17 am
Location: Lancashire, UK

Re: Python, Tkinter and GPIO

Mon Dec 11, 2017 7:16 pm

@scotty101
Thank you once again.
It's so simple when you know how!

I was trying to go one step at a time regarding the LED switching and colour changing.
The counter now works with the change you have suggested.

The print statements I put in to show the route the program takes, have worked well.

RDS
Posts: 623
Joined: Tue Oct 06, 2015 8:17 am
Location: Lancashire, UK

Re: Python, Tkinter and GPIO

Sun Dec 24, 2017 6:53 pm

@scotty101
I feel that I am making progress, although I recognise it is nothing compared to your program.
So far, I have understood and modified the text and font in the tkinter window and added attributes to the tkinter window so that it always starts in the same screen position. I have also successfully added the few program lines from my original program that pick up the starting point in terms of the number of pulses already detected in a particular day to ensure that if the program is stopped and started, that it will carry on from where it left off, in terms of the pulse count.

However, despite reading many many articles about classes I am no nearer to understanding them than I was before I started. I think I understand the principles but for a program like mine, I have not yet understood why they are used. I am familiar with defining and using functions and I have used them extensively in my own program.

I have tried to add further aspects from my own program but I am constantly getting errors such as a variable I have used is not defined and I think this is because I have not introduced them in the right place in the program.

My simple question after all that is, where do I introduce new features, so as not to upset the structure of the program.
The code I referred to above, namely that to define a starting point, I added in to the class App(Frame) but although it worked it did not seem right to place it in the middle of a class.

scotty101
Posts: 2795
Joined: Fri Jun 08, 2012 6:03 pm

Re: Python, Tkinter and GPIO

Mon Dec 25, 2017 7:10 pm

Short answer since it's Christmas.
To add a new property to the App class you could do something like

Code: Select all

class App(Frame):
    def __init__(self,other_stuff):
        #Existing stuff
        self.new_property = 0
        
Now if you want to use the new_property from inside the class you use self.new_property or outside the class like such

Code: Select all

app = App()

app.new_property = 7
Difficult to explain all of Object Oriented programming in a post.
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

RDS
Posts: 623
Joined: Tue Oct 06, 2015 8:17 am
Location: Lancashire, UK

Re: Python, Tkinter and GPIO

Tue Dec 26, 2017 9:11 am

@scotty101
Thanks.
Following your post, I have found some tutorials about Object Oriented Programming on U-Tube that I am hoping will help.
I think the main thought I had was that I did not see why classes were used in a program that does not have multiple occurrences, unlike your GPIO monitoring program where you draw a shape for each GPIO.

RDS
Posts: 623
Joined: Tue Oct 06, 2015 8:17 am
Location: Lancashire, UK

Re: Python, Tkinter and GPIO

Thu Dec 28, 2017 7:04 pm

@scotty101
I have now got the counter working using the BOTH version of the edge detect interrupt.
It counts 2 for every occurrence but thinking about it, is that not would you would expect. I keep the pin high, except when a pulse is detected (to prevent spurious readings). When a pulse is detected, the pin goes low and returns high immediately. So, detecting BOTH fall and rise will surely result in the double count I am experiencing.

Do you agree that I need to just set the interrupt to detect the FALL and put some form of ELSE command to switch the LED on the tkinter window off again.

scotty101
Posts: 2795
Joined: Fri Jun 08, 2012 6:03 pm

Re: Python, Tkinter and GPIO

Thu Dec 28, 2017 10:03 pm

Detect both edges but in the callback only increment the counter if the pin currently low.
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

RDS
Posts: 623
Joined: Tue Oct 06, 2015 8:17 am
Location: Lancashire, UK

Re: Python, Tkinter and GPIO

Mon Jan 01, 2018 11:08 pm

@scotty101
Unfortunately, that did not work but I wonder if when the falling edge is detected, the actual pin voltage will be less than 3.3v but not (yet) equal to 0v.
I have reverted back to just detecting the FALLING pulse (rather than BOTH) and this counts perfectly but I have still not managed to get the LED to turn off. As soon as the first pulse is detected, it comes on and stays on.

Return to “Python”

Who is online

Users browsing this forum: No registered users and 7 guests