Vortex
Vortex

Reputation: 9

Can't get Python GPIO Emulator to work on M2 Mac

I'm trying to run a GPIO emulator in Python on my M2 MacBook Air. The same application works fine on Windows, but will not run on my MacBook.

Here's the error:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'NSWindow should only be instantiated on the main thread!
First throw call stack:
(
        0   CoreFoundation                      0x000000019692ee80 __exceptionPreprocess + 176
        1   libobjc.A.dylib                     0x0000000196416cd8 objc_exception_throw + 88
        2   CoreFoundation                      0x0000000196953534 _CFBundleGetValueForInfoKey + 0
        3   AppKit                              0x000000019a436c4c -[NSWindow _initContent:styleMask:backing:defer:contentView:] + 260
        4   AppKit                              0x000000019a436b3c -[NSWindow initWithContentRect:styleMask:backing:defer:] + 48
        5   libtk8.6.dylib                      0x0000000101c67b5c TkMacOSXMakeRealWindowExist + 496
        6   libtk8.6.dylib                      0x0000000101c6785c TkWmMapWindow + 56
        7   libtk8.6.dylib                      0x0000000101bd8ffc MapFrame + 76
        8   libtcl8.6.dylib                     0x0000000101b0c47c TclServiceIdle + 84
        9   libtcl8.6.dylib                     0x0000000101af05d0 Tcl_DoOneEvent + 296
        10  libtk8.6.dylib                      0x0000000101c5b81c TkpInit + 712
        11  libtk8.6.dylib                      0x0000000101bd2030 Initialize + 2372
        12  _tkinter.cpython-311-darwin.so      0x0000000100fe63bc Tcl_AppInit + 80
        13  _tkinter.cpython-311-darwin.so      0x0000000100fe0888 Tkapp_New + 592
        14  _tkinter.cpython-311-darwin.so      0x0000000100fe025c _tkinter_create + 608
        15  Python                              0x00000001015e3e18 cfunction_vectorcall_FASTCALL + 80
        16  Python                              0x0000000101674950 _PyEval_EvalFrameDefault + 40904
        17  Python                              0x0000000101678e38 _PyEval_Vector + 116
        18  Python                              0x0000000101598c0c _PyObject_FastCallDictTstate + 96
        19  Python                              0x0000000101602654 slot_tp_init + 188
        20  Python                              0x00000001015fab34 type_call + 136
        21  Python                              0x0000000101598964 _PyObject_MakeTpCall + 128
        22  Python                              0x0000000101674a8c _PyEval_EvalFrameDefault + 41220
        23  Python                              0x0000000101678e38 _PyEval_Vector + 116
        24  Python                              0x000000010159beb0 method_vectorcall + 384
        25  Python                              0x000000010172d728 thread_run + 168
        26  Python                              0x00000001016ce110 pythread_wrapper + 48
        27  libsystem_pthread.dylib             0x00000001967d42e4 _pthread_start + 136
        28  libsystem_pthread.dylib             0x00000001967cf0fc thread_start + 8
)
libc++abi: terminating due to uncaught exception of type NSException

Here's the code of the application:

EmulatorGUI.py:

from tkinter import *
import tkinter as tk
from PIN import PIN
from TypeChecker import typeassert
import threading
import time


#http://www.tutorialspoint.com/python/tk_button.htm


dictionaryPins = {}
dictionaryPinsTkinter = {}

GPIONames=["14","15","18","23","24","25","8","7","12","16","20","21","2","3","4","17","27","22","10","9","11","5","6","13","19","26"]
    
class App(threading.Thread):
    

        
    def __init__(self):
        threading.Thread.__init__(self)
        self.start()

        

    def callback(self):
        self.root.quit()

    def run(self):
        self.root = tk.Tk()
        self.root.wm_title("GPIO EMULATOR")
        self.root.protocol("WM_DELETE_WINDOW", self.callback)


            
        #5V
        pin2label = Label(text="5V", fg="red")
        pin2label.grid(row=0, column=0, padx=(10, 10))
        
        #5V
        pin4label = Label(text="5V", fg="red")
        pin4label.grid(row=0, column=1, padx=(10, 10))

        #GND
        pin6label = Label(text="GND", fg="black")
        pin6label.grid(row=0, column=2, padx=(10, 10))
        
        #GPIO14
        pin8btn = Button(text="GPIO14\nOUT=0", command="14", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin8btn.grid(row=0, column=3, padx=(10, 10),pady=(5,5))

        dictionaryPinsTkinter["14"] = pin8btn


        #GPIO15
        pin10btn = Button(text="GPIO15\nOUT=0", command="15", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin10btn.grid(row=0, column=4, padx=(10, 10))

        dictionaryPinsTkinter["15"] =pin10btn

        
        #GPIO18
        pin12btn = Button(text="GPIO18\nOUT=0", command="18",  padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin12btn.grid(row=0, column=5, padx=(10, 10))

        dictionaryPinsTkinter["18"] = pin12btn
        

        #GND
        pin14label = Label(text="GND", fg="black")
        pin14label.grid(row=0, column=6, padx=(10, 10))

        #GPIO23
        pin16btn = Button(text="GPIO23\nOUT=0", command="23", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin16btn.grid(row=0, column=7, padx=(10, 10))

        dictionaryPinsTkinter["23"] = pin16btn


        #GPIO24
        pin18btn = Button(text="GPIO24\nOUT=0",command="24",  padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin18btn.grid(row=0, column=8, padx=(10, 10))

        dictionaryPinsTkinter["24"] = pin18btn

        
        #GND
        pin20label = Label(text="GND", fg="black")
        pin20label.grid(row=0, column=9, padx=(10, 10))

        #GPIO25
        pin22btn = Button(text="GPIO25\nOUT=0", command="25", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin22btn.grid(row=0, column=10, padx=(10, 10))

        dictionaryPinsTkinter["25"] = pin22btn

        
        #GPIO08
        pin24btn = Button(text="GPIO8\nOUT=0", command="8", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin24btn.grid(row=0, column=11, padx=(10, 10))

        dictionaryPinsTkinter["8"] = pin24btn


        #GPIO07
        pin26btn = Button(text="GPIO7\nOUT=0", command="7",  padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin26btn.grid(row=0, column=12, padx=(10, 10))

        dictionaryPinsTkinter["7"] = pin26btn


        #ID_SC
        pin28label = Label(text="ID_SC", fg="black")
        pin28label.grid(row=0, column=13, padx=(10, 10))

        #GND
        pin30label = Label(text="GND", fg="black")
        pin30label.grid(row=0, column=14, padx=(10, 10))

        #GPIO12
        pin32btn = Button(text="GPIO12\nOUT=0", command="12", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin32btn.grid(row=0, column=15, padx=(10, 10))

        dictionaryPinsTkinter["12"] = pin32btn


        #GND
        pin34label = Label(text="GND", fg="black")
        pin34label.grid(row=0, column=16, padx=(10, 10))

        #GPIO16
        pin36btn = Button(text="GPIO16\nOUT=0", command="16",  padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin36btn.grid(row=0, column=17, padx=(10, 10))

        dictionaryPinsTkinter["16"] = pin36btn

        #GPIO20
        pin38btn = Button(text="GPIO20\nOUT=0", command="20", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin38btn.grid(row=0, column=18, padx=(10, 10))

        dictionaryPinsTkinter["20"] = pin38btn
        
        #GPIO21
        pin40btn = Button(text="GPIO21\nOUT=0", command="21", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin40btn.grid(row=0, column=19, padx=(10, 10))

        
        dictionaryPinsTkinter["21"] = pin40btn

        #####bottom

        #3V3
        pin1label = Label(text="3V3", fg="dark orange")
        pin1label.grid(row=1, column=0, padx=(10, 10), pady=(5,5))

        #GPIO02
        pin03btn = Button(text="GPIO2\nOUT=0",command="2", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin03btn.grid(row=1, column=1, padx=(10, 10),pady=(5,5))

        dictionaryPinsTkinter["2"] =pin03btn

        #GPIO03
        pin05btn = Button(text="GPIO3\nOUT=0", command="3", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin05btn.grid(row=1, column=2, padx=(10, 10))

        dictionaryPinsTkinter["3"] = pin05btn

        #GPIO04
        pin07btn = Button(text="GPIO4\nOUT=0", command="4", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin07btn.grid(row=1, column=3, padx=(10, 10))

        dictionaryPinsTkinter["4"] = pin07btn

        #gnd
        pin09label = Label(text="GND", fg="black")
        pin09label.grid(row=1, column=4, padx=(10, 10))

        #GPIO17
        pin11btn = Button(text="GPIO17\nOUT=0", command="17", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin11btn.grid(row=1, column=5, padx=(10, 10))

        dictionaryPinsTkinter["17"] = pin11btn

        #GPIO27
        pin13btn = Button(text="GPIO27\nOUT=0", command="27", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin13btn.grid(row=1, column=6, padx=(10, 10))

        dictionaryPinsTkinter["27"] = pin13btn

        #GPIO22
        pin15btn = Button(text="GPIO22\nOUT=0", command="22", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin15btn.grid(row=1, column=7, padx=(10, 10))

        dictionaryPinsTkinter["22"] = pin15btn

        #3V3
        pin17label = Label(text="3V3", fg="dark orange")
        pin17label.grid(row=1, column=8, padx=(10, 10))

        #GPIO10
        pin19btn = Button(text="GPIO10\nOUT=0", command="10",  padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin19btn.grid(row=1, column=9, padx=(10, 10))

        dictionaryPinsTkinter["10"] = pin19btn

        #GPIO09
        pin21btn = Button(text="GPIO9\nOUT=0", command="9", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin21btn.grid(row=1, column=10, padx=(10, 10))

        dictionaryPinsTkinter["9"] = pin21btn

        #GPIO11
        pin23btn = Button(text="GPIO11\nOUT=0", command="11", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin23btn.grid(row=1, column=11, padx=(10, 10))

        dictionaryPinsTkinter["11"] = pin23btn

        #gnd
        pin25label = Label(text="GND", fg="black")
        pin25label.grid(row=1, column=12, padx=(10, 10))

        #ID_SD
        pin27label = Label(text="ID_SD", fg="black")
        pin27label.grid(row=1, column=13, padx=(10, 10))

        #GPIO05
        pin29btn = Button(text="GPIO5\nOUT=0", command="5", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin29btn.grid(row=1, column=14, padx=(10, 10))

        dictionaryPinsTkinter["5"] = pin29btn

        #GPIO06
        pin31btn = Button(text="GPIO6\nOUT=0", command="6", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin31btn.grid(row=1, column=15, padx=(10, 10))

        dictionaryPinsTkinter["6"]=pin31btn

        #GPIO13
        pin33btn = Button(text="GPIO13\nOUT=0", command="13", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin33btn.grid(row=1, column=16, padx=(10, 10))

        dictionaryPinsTkinter["13"] = pin33btn

        #GPIO19
        pin35btn = Button(text="GPIO19\nOUT=0", command="19", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin35btn.grid(row=1, column=17, padx=(10, 10))

        dictionaryPinsTkinter["19"] = pin35btn
        
            
        #GPIO26
        pin37btn = Button(text="GPIO26\nOUT=0", command="26", padx ="1px", pady="1px", bd="0px", fg="blue", relief="sunken", activeforeground="blue")
        pin37btn.grid(row=1, column=18, padx=(10, 10))
        
        
        dictionaryPinsTkinter["26"] = pin37btn

        #gnd
        pin39label = Label(text="GND", fg="black")
        pin39label.grid(row=1, column=19, padx=(10, 10))


        self.root.geometry('%dx%d+%d+%d' % (1300, 100, 0, 0))
       
        self.root.mainloop()       

        

##        button1.unbind("<Button-1>")
     

app = App()


def toggleButton(gpioID):
    #print(gpioID)
    objBtn = dictionaryPinsTkinter[str(gpioID)]
    objPin = dictionaryPins[str(gpioID)]
    
    if(objPin.In == "1"):
        objPin.In = "0"
    elif(objPin.In == "0"):
        objPin.In = "1"
        
    objBtn["text"] = "GPIO" + str(gpioID) + "\nIN=" + str(objPin.In)
    
    
  
def buttonClick(self):
##    print("clicked")
    gpioID = (self.widget.config('command')[-1])
    toggleButton(gpioID)
    
    

def buttonClickRelease(self):
##    print("released")
    gpioID = (self.widget.config('command')[-1])
    toggleButton(gpioID)
    




    
def drawGPIOOut(gpioID):
    global dictionaryPins
    global dictionaryPinsTkinter

    gpioID = str(gpioID)
    objPin = dictionaryPins[gpioID]
    objBtn = dictionaryPinsTkinter[gpioID]

    

    if(objPin.SetMode == "OUT"):
        objBtn["text"] = "GPIO" + str(gpioID) + "\nOUT=" + str(objPin.Out)
        if(str(objPin.Out) == "1"):
            objBtn.configure(background='tan2')
            objBtn.configure(activebackground='tan2')
        else:
            objBtn.configure(background='DarkOliveGreen3')
            objBtn.configure(activebackground='DarkOliveGreen3')
            
            
    
    
    

def drawBindUpdateButtonIn(gpioID,In):
    objBtn = dictionaryPinsTkinter[gpioID]
    objBtn.configure(background='gainsboro')
    objBtn.configure(activebackground='gainsboro')
    objBtn.configure(relief='raised')
    objBtn.configure(bd="1px")
    objBtn["text"] = "GPIO" + str(gpioID) + "\nIN=" + str(In)
    objBtn.bind("<Button-1>", buttonClick)
    objBtn.bind("<ButtonRelease-1>", buttonClickRelease)


class GPIO:

  
    #constants
    LOW = 0 
    HIGH = 1
    OUT = 2
    IN = 3
    PUD_OFF = 4
    PUD_DOWN = 5
    PUD_UP = 6
    BCM = 7

    #flags
    setModeDone = False

    #Extra functions
    def checkModeValidator():
        if(GPIO.setModeDone == False):
            raise Exception('Setup your GPIO mode. Must be set to BCM')

    
    #GPIO LIBRARY Functions
    @typeassert(int)
    def setmode(mode):
        time.sleep(1)
        if(mode == GPIO.BCM):
            GPIO.setModeDone = True
        else:
            GPIO.setModeDone = False

    @typeassert(bool)
    def setwarnings(flag):
        pass

    @typeassert(int,int,int,int)        
    def setup(channel, state, initial=-1,pull_up_down=-1):
        global dictionaryPins
        
        GPIO.checkModeValidator()

        if str(channel) not in GPIONames:
            raise Exception('GPIO ' + str(channel) + ' does not exist')

        #check if channel is already setup
        if str(channel) in dictionaryPins:
            raise Exception('GPIO is already setup')

        if(state == GPIO.OUT):
            #GPIO is set as output, default OUT 0
            objTemp =  PIN("OUT")
            if(initial == GPIO.HIGH):
                objTemp.Out = "1"
                
            dictionaryPins[str(channel)] =objTemp
            drawGPIOOut(channel)
            
        elif(state == GPIO.IN):
            #set input
            objTemp =  PIN("IN")
            if(pull_up_down == -1):
                objTemp.pull_up_down = "PUD_DOWN" #by default pud_down
                objTemp.In = "0"
            elif(pull_up_down == GPIO.PUD_DOWN):
                objTemp.pull_up_down = "PUD_DOWN"
                objTemp.In = "0"
             
            elif(pull_up_down == GPIO.PUD_UP):
                objTemp.pull_up_down = "PUD_UP"
                objTemp.In = "1"
                
            drawBindUpdateButtonIn(str(channel),objTemp.In)
            dictionaryPins[str(channel)] =objTemp
            
            
        
        
        

    @typeassert(int,int)
    def output(channel, outmode):
        global dictionaryPins
        channel = str(channel)
        
        GPIO.checkModeValidator()


        if channel not in dictionaryPins:
            #if channel is not setup
            raise Exception('GPIO must be setup before used')
        else:
            objPin = dictionaryPins[channel]
            if(objPin.SetMode == "IN"):
                #if channel is setup as IN and used as an OUTPUT
                raise Exception('GPIO must be setup as OUT')

        
        if(outmode != GPIO.LOW and outmode != GPIO.HIGH):
            raise Exception('Output must be set to HIGH/LOW')
            
        objPin = dictionaryPins[channel]
        if(outmode == GPIO.LOW):
            objPin.Out = "0"
        elif(outmode == GPIO.HIGH):
            objPin.Out = "1"

        
        drawGPIOOut(channel)


    @typeassert(int)
    def input(channel):
        global dictionaryPins
        channel = str(channel)

        GPIO.checkModeValidator()


        if channel not in dictionaryPins:
            #if channel is not setup
            raise Exception('GPIO must be setup before used')
        else:
            objPin = dictionaryPins[channel]
            if(objPin.SetMode == "OUT"):
                #if channel is setup as OUTPUT and used as an INPUT
                raise Exception('GPIO must be setup as IN')

        objPin = dictionaryPins[channel]
        if(objPin.In == "1"):
            return True
        elif(objPin.Out == "0"):
            return False


    
    def cleanup():
        pass

PIN.py:

class PIN():
    SetMode = "None" #IN/OUT/NONE
    Out = "0"
    pull_up_down = "PUD_OFF" #PUD_UP/PUD_DOWN/PUD_OFF
    In = "1"

    def __init__(self, SetMode):
        self.SetMode = SetMode
        self.Out = "0"

Test.py:

from EmulatorGUI import GPIO
# import RPi.GPIO as GPIO
import time
import traceback


def main():
    GPIO.setmode(GPIO.BCM)

    GPIO.setwarnings(False)

    GPIO.setup(4, GPIO.OUT)
    GPIO.setup(17, GPIO.OUT, initial=GPIO.LOW)
    GPIO.setup(18, GPIO.OUT, initial=GPIO.LOW)
    GPIO.setup(21, GPIO.OUT, initial=GPIO.LOW)
    GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(15, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
    GPIO.setup(24, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
    GPIO.setup(26, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

    while True:
        if not GPIO.input(23):
            GPIO.output(4, GPIO.HIGH)
            GPIO.output(17, GPIO.HIGH)
            time.sleep(1)

        if GPIO.input(15):
            GPIO.output(18, GPIO.HIGH)
            GPIO.output(21, GPIO.HIGH)
            time.sleep(1)

        if GPIO.input(24):
            GPIO.output(18, GPIO.LOW)
            GPIO.output(21, GPIO.LOW)
            time.sleep(1)

        if GPIO.input(26):
            GPIO.output(4, GPIO.LOW)
            GPIO.output(17, GPIO.LOW)
            time.sleep(1)

    GPIO.cleanup()  # this ensures a clean exit


main()

TypeChecker.py:

from inspect import signature
from functools import wraps

def typeassert(*ty_args, **ty_kwargs):
    def decorate(func):
        # If in optimized mode, disable type checking
        if not __debug__:
            return func

        # Map function argument names to supplied types
        sig = signature(func)
        bound_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments

        @wraps(func)
        def wrapper(*args, **kwargs):
            bound_values = sig.bind(*args, **kwargs)
            # Enforce type assertions across supplied arguments
            for name, value in bound_values.arguments.items():
                if name in bound_types:
                    if not isinstance(value, bound_types[name]):
                      raise TypeError(
                        'Argument {} must be {}'.format(name, bound_types[name])
                        )
            return func(*args, **kwargs)
        return wrapper
    return decorate

The program runs from Test.py. I'm using Python 3.11.1 installed from Brew. Thanks in advance.

Upvotes: -1

Views: 23

Answers (0)

Related Questions