├── gpiotest.png ├── README.md └── gpiotest.py /gpiotest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RomanMindlin/gpiotest/HEAD/gpiotest.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gpiotest.py 2 | 3 | Small console utility to do gpio pin monitor & Raspberry Pi DYI projects debugging 4 | 5 | ![screenshot](https://github.com/kgbplus/gpiotest/blob/master/gpiotest.png) 6 | 7 | To start, enter ```sudo python gpiotest.py``` from console 8 | 9 | (If you got addstr() error - just increase size of terminal window) 10 | ## Keyboard commands: 11 | 12 | Q - Quit program 13 | 14 | P - Pause program, to continue press 'P' again 15 | 16 | D - Set debounce value (ms) 17 | 18 | U - Configure pullup resistors for input pins. Enter GPIO line number (not pin number on the plug), then enter value for pullup (0 for GPIO.PUD_DOWN or 1 GPIO.PUD_UP) 19 | 20 | O - Press to output something. Enter GPIO line number and value. Pin became 'output' and program stop monitoring it's state. 21 | 22 | I - Return pin to 'input' state. Program will show it's state. 23 | 24 | ## Pin indication: 25 | 1. Input pin not inversed, output inversed 26 | 2. After equal sign - pin state True/False (1/0) 27 | 3. In the brackets - pullup resistor state (^) - High or (v) - Low 28 | 29 | ## Command line options: 30 | You can use ```-g``` or ```--gpio_num``` switch with ```17``` or ```26``` parameter to override automatic hardware detection. 31 | 32 | ## Questions 33 | Feel free to contact me on [Raspberry Pi forum](https://www.raspberrypi.org/forums/viewtopic.php?f=37&t=167609) or by [e-mail](roman@mindlin.ru) 34 | -------------------------------------------------------------------------------- /gpiotest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # gpiotest.py 5 | # 6 | # Copyright 2016 Roman Mindlin 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | 25 | try: 26 | import RPi.GPIO as GPIO 27 | except RuntimeError: 28 | print("Error importing RPi.GPIO! This is probably because you need superuser privileges. You can achieve this by using 'sudo' to run your script") 29 | 30 | import sys 31 | import getopt 32 | import curses 33 | import random 34 | import time 35 | from datetime import datetime 36 | 37 | def termOn(): 38 | # Enable character buffering and echo 39 | curses.nocbreak() 40 | curses.echo() 41 | 42 | def termOff(): 43 | # Disable character buffering and echo 44 | curses.cbreak() 45 | curses.noecho() 46 | 47 | def MainScreen(): 48 | #Draw main screen 49 | #coords - screen position of placeholders for pin states 50 | coords = [[6,15],[7,15],[8,15],[9,15],[10,15],[11,15],[12,15],[13,15],[14,15], 51 | [6,42],[7,42],[8,42],[9,42],[10,42],[11,42],[12,42],[13,42],[14,42], 52 | [6,69],[7,69],[8,69],[9,69],[10,69],[11,69],[12,69],[13,69]] 53 | 54 | myscreen.erase() 55 | myscreen.addstr(1,0, "--------------------------------------------------------------------------------") 56 | myscreen.addstr(2,0, "| * Raspberry Pi GPIO monitor * |") 57 | myscreen.addstr(3,0, "--------------------------------------------------------------------------------") 58 | myscreen.addstr(4,0, "| * Debounce |") 59 | myscreen.addstr(4,2, RaspiModel + " detected (" + str(gpio_num) + " lines)") 60 | myscreen.addstr(4,68, str(debounce) + " ms") 61 | myscreen.addstr(5,0, "--------------------------------------------------------------------------------") 62 | if (gpio_num == 17): 63 | myscreen.addstr(6,0, "| GPIO0 = | GPIO15 = | |") 64 | myscreen.addstr(7,0, "| GPIO1 = | GPIO17 = | |") 65 | myscreen.addstr(8,0, "| GPIO4 = | GPIO18 = | |") 66 | myscreen.addstr(9,0, "| GPIO7 = | GPIO21 = | |") 67 | myscreen.addstr(10,0, "| GPIO8 = | GPIO22 = | |") 68 | myscreen.addstr(11,0, "| GPIO9 = | GPIO23 = | |") 69 | myscreen.addstr(12,0, "| GPIO10 = | GPIO24 = | |") 70 | myscreen.addstr(13,0, "| GPIO11 = | GPIO25 = | |") 71 | myscreen.addstr(14,0, "| GPIO14 = | | |") 72 | else: 73 | myscreen.addstr(6,0, "| GPIO2 = | GPIO11 = | GPIO20 = |") 74 | myscreen.addstr(7,0, "| GPIO3 = | GPIO12 = | GPIO21 = |") 75 | myscreen.addstr(8,0, "| GPIO4 = | GPIO13 = | GPIO22 = |") 76 | myscreen.addstr(9,0, "| GPIO5 = | GPIO14 = | GPIO23 = |") 77 | myscreen.addstr(10,0, "| GPIO6 = | GPIO15 = | GPIO24 = |") 78 | myscreen.addstr(11,0, "| GPIO7 = | GPIO16 = | GPIO25 = |") 79 | myscreen.addstr(12,0, "| GPIO8 = | GPIO17 = | GPIO26 = |") 80 | myscreen.addstr(13,0, "| GPIO9 = | GPIO18 = | GPIO27 = |") 81 | myscreen.addstr(14,0, "| GPIO10 = | GPIO19 = | |") 82 | myscreen.addstr(15,0, "--------------------------------------------------------------------------------") 83 | myscreen.addstr(21,0, "--------------------------------------------------------------------------------") 84 | myscreen.addstr(23,0, "Q = Quit P = Pause D = Debounce U = pullUp O = Output I = Input") 85 | 86 | 87 | #Print states and pullup status 88 | for i in range(gpio_num): 89 | state_text = "True" if gpio_state[i] else "False" 90 | state_text = state_text + ("(^)" if gpio_pud[i] else "(v)") 91 | myscreen.addstr(coords[i][0], coords[i][1], state_text, 92 | curses.A_REVERSE if gpio_inout[i] else curses.A_NORMAL) 93 | 94 | #Activity indicator 95 | myscreen.addstr(0,0, chr(int(random.random()*32) + 32)) 96 | 97 | # Print log strings 98 | logwindow.erase() 99 | for i in range(0,5): 100 | logwindow.insstr(i,0,log[i]) 101 | 102 | #Set cursor position 103 | myscreen.move(22,0) 104 | myscreen.refresh() 105 | 106 | def SendToLog(LogMessage): 107 | # Rotate log lines 108 | global log 109 | for i in range(0,5): 110 | log[i] = log[i+1] 111 | log[4] = LogMessage 112 | 113 | def PrintMsg(Msg): 114 | # Print messages on bottom line of screen 115 | msgwindow.erase() 116 | msgwindow.addstr(Msg) 117 | msgwindow.refresh() 118 | 119 | def CheckKeys(): 120 | # Keyboard events 121 | global debounce 122 | global on_pause 123 | 124 | myscreen.nodelay(1) 125 | key = myscreen.getch() 126 | myscreen.nodelay(0) 127 | 128 | if key == ord('q') or key == ord('Q'): 129 | raise KeyboardInterrupt 130 | elif key == ord('p') or key == ord('P'): 131 | PrintMsg("Paused. Press P again to continue") 132 | on_pause = 1 133 | while on_pause: 134 | key = msgwindow.getch() 135 | if key == ord('p') or key == ord('P'): 136 | on_pause = 0 137 | elif key == ord('d') or key == ord('D'): 138 | try: 139 | termOn() 140 | PrintMsg("Enter debounce value (ms): ") 141 | debounce_ = int(msgwindow.getstr()) 142 | if (debounce_ < 0 or debounce_ > 5000): 143 | raise ValueError 144 | if (debounce_ != debounce): 145 | debounce = debounce_ 146 | initGpio() 147 | termOff() 148 | except ValueError: 149 | PrintMsg("Value not in range") 150 | termOff() 151 | msgwindow.getch() 152 | elif key == ord('u') or key == ord('U'): 153 | try: 154 | termOn() 155 | PrintMsg("Enter GPIO line number: ") 156 | channel = int(msgwindow.getstr()) 157 | if not (channel in gpio_ch): 158 | raise ValueError 159 | num = gpio_ch.index(channel) 160 | if (gpio_inout[num]): 161 | raise IOError 162 | PrintMsg("Enter 0 for GPIO.PUD_DOWN or 1 GPIO.PUD_UP: ") 163 | pud = int(msgwindow.getstr()) 164 | if (pud != 1 and pud != 0): 165 | raise ValueError 166 | gpio_pud[num] = pud 167 | initGpio() 168 | termOff() 169 | except ValueError: 170 | PrintMsg("Value not in range") 171 | termOff() 172 | msgwindow.getch() 173 | except IOError: 174 | PrintMsg("Output line cannot be pulled up") 175 | termOff() 176 | msgwindow.getch() 177 | elif key == ord('o') or key == ord('O'): 178 | try: 179 | termOn() 180 | PrintMsg("Enter GPIO line number: ") 181 | channel = int(msgwindow.getstr()) 182 | if not (channel in gpio_ch): 183 | raise ValueError 184 | num = gpio_ch.index(channel) 185 | PrintMsg("Enter 0 for GPIO.LOW or 1 GPIO.HIGH: ") 186 | val = int(msgwindow.getstr()) 187 | if (val != 1 and val != 0): 188 | raise ValueError 189 | gpio_inout[num] = 1 190 | gpio_state[num] = val 191 | GPIO.setup(channel, GPIO.OUT) 192 | GPIO.output(channel,GPIO.HIGH if val else GPIO.LOW) 193 | termOff() 194 | except ValueError: 195 | PrintMsg("Value not in range") 196 | termOff() 197 | msgwindow.getch() 198 | elif key == ord('i') or key == ord('I'): 199 | try: 200 | termOn() 201 | PrintMsg("Enter GPIO line number: ") 202 | channel = int(msgwindow.getstr()) 203 | if not (channel in gpio_ch): 204 | raise ValueError 205 | num = gpio_ch.index(channel) 206 | gpio_inout[num] = 0 207 | initGpio() 208 | termOff() 209 | except ValueError: 210 | PrintMsg("Value not in range") 211 | termOff() 212 | msgwindow.getch() 213 | 214 | def getPinFunctionName(pin): 215 | # Unused 216 | functions = {GPIO.IN:'Input', 217 | GPIO.OUT:'Output', 218 | GPIO.I2C:'I2C', 219 | GPIO.SPI:'SPI', 220 | GPIO.HARD_PWM:'HARD_PWM', 221 | GPIO.SERIAL:'Serial', 222 | GPIO.UNKNOWN:'Unknown'} 223 | 224 | return functions[GPIO.gpio_function(pin)] 225 | 226 | def getRaspiModel(argument): 227 | #Detect Raspberry Pi model 228 | switcher = { 229 | "0002": "Model B Revision 1.0 256Mb", 230 | "0003": "Model B Revision 1.0 + ECN0001 256Mb", 231 | "0004": "Model B Revision 2.0 256Mb", 232 | "0005": "Model B Revision 2.0 256Mb", 233 | "0006": "Model B Revision 2.0 256Mb", 234 | "0007": "Model A 256Mb", 235 | "0008": "Model A 256Mb", 236 | "0009": "Model A 256Mb", 237 | "000d": "Model B Revision 2.0 512Mb", 238 | "000e": "Model B Revision 2.0 512Mb", 239 | "e": "Model B Revision 2.0 512Mb", 240 | "000f": "Model B Revision 2.0 512Mb", 241 | "0010": "Model B+ 512Mb", 242 | "0012": "Model A+ 256Mb", 243 | "0013": "Model B+ 512Mb", 244 | "13": "Model B+ 512Mb", # https://github.com/kgbplus/gpiotest/issues/7 245 | "0015": "Model A+ 256/512Mb", 246 | "a01040": "2 Model B Revision 1.0 1Gb", 247 | "a01041": "2 Model B Revision 1.1 1Gb", 248 | "a21041": "2 Model B Revision 1.1 1Gb", 249 | "a22042": "2 Model B (with BCM2837) 1Gb", 250 | "900021": "Model A+ 512Mb", 251 | "900032": "Model B+ 512Mb", 252 | "900092": "Zero Revision 1.2 512Mb", 253 | "900093": "Zero Revision 1.3 512Mb", 254 | "920093": "Zero Revision 1.3 512Mb", 255 | "9000c1": "Zero W Revision 1.1 512Mb", 256 | "a02082": "3 Model B 1Gb", 257 | "a22082": "3 Model B 1Gb", 258 | "a32082": "3 Model B 1Gb", 259 | "a020d3": "3 Model B+ 1Gb", 260 | "9020e0": "3 Model A+ 512Mb", 261 | "a03111": "4 Model B 1Gb", 262 | "b03111": "4 Model B 2Gb", 263 | "b03112": "4 Model B 2Gb", 264 | "bo3114": "4 Model B 2Gb", 265 | "b03115": "4 Model B 2Gb", 266 | "c03111": "4 Model B 4Gb", 267 | "c03112": "4 Model B 4Gb", 268 | "c03114": "4 Model B 4Gb", 269 | "c03115": "4 Model B Revision 1.5 4Gb", 270 | "d03114": "4 Model B 8Gb", 271 | "c03130": "Pi 400 4Gb", 272 | "b03140": "Compute Module 4 2Gb", 273 | } 274 | return switcher.get(argument, "not supported") 275 | 276 | def getGpioNum(argument): 277 | #Return number of GPIO lines 278 | switcher = { 279 | "0002": 17, 280 | "0003": 17, 281 | "0004": 17, 282 | "0005": 17, 283 | "0006": 17, 284 | "0007": 17, 285 | "0008": 17, 286 | "0009": 17, 287 | "000d": 17, 288 | "000e": 17, 289 | "e": 17, 290 | "000f": 17, 291 | "0010": 26, 292 | "0012": 26, 293 | "0013": 26, 294 | "13": 26, 295 | "0015": 26, 296 | "a01040": 26, 297 | "a01041": 26, 298 | "a21041": 26, 299 | "a22042": 26, 300 | "900021": 26, 301 | "900032": 26, 302 | "900092": 26, 303 | "900093": 26, 304 | "920093": 26, 305 | "9000c1": 26, 306 | "a02082": 26, 307 | "a22082": 26, 308 | "a32082": 26, 309 | "a020d3": 26, 310 | "9020e0": 26, 311 | "a03111": 26, 312 | "b03111": 26, 313 | "b03115": 26, 314 | "c03111": 26, 315 | "c03112": 26, 316 | "b03112": 26, 317 | "c03114": 26, 318 | "c03115": 26, 319 | "d03114": 26, 320 | "c03130": 26, 321 | "b03140": 26, 322 | } 323 | return switcher.get(argument, 17) 324 | 325 | def initGpio(firstrun=0): 326 | curses.savetty() #Save screen 327 | 328 | #Init GPIO 329 | if not firstrun: 330 | GPIO.cleanup() 331 | GPIO.setmode(GPIO.BCM) 332 | GPIO.setwarnings(0) 333 | 334 | #Init GPIO pins, set event_detect callbacks, save initial states etc. 335 | for i,channel in enumerate(gpio_ch): 336 | if not gpio_inout[i]: 337 | GPIO.setup(channel, GPIO.IN, pull_up_down=GPIO.PUD_DOWN if gpio_pud[i] == 0 else GPIO.PUD_UP) 338 | GPIO.add_event_detect(channel, GPIO.BOTH, callback = gpio_callback, bouncetime = debounce) 339 | gpio_state[i] = GPIO.input(channel) # Primary state 340 | 341 | curses.resetty() # Restore screen 342 | 343 | def gpio_callback(channel): 344 | # Callback fucntion - waiting for event, changing gpio states 345 | global gpio_state 346 | 347 | if not on_pause: 348 | gpio_state[gpio_ch.index(channel)] = GPIO.input(channel) 349 | SendToLog(datetime.now().strftime("%Y-%b-%d %H:%M:%S")+": Channel " + str(channel) + 350 | " changed " + ("(on)" if GPIO.input(channel) else "(off)")+"\n") 351 | 352 | try: 353 | #Check command line options 354 | gpio_num = 0 355 | opts, args = getopt.getopt(sys.argv[1:],"hg:",["help","gpio_num="]) 356 | except getopt.GetoptError: 357 | print('Usage: gpiotest.py [--gpio_num ]') 358 | sys.exit(2) 359 | for opt, arg in opts: 360 | if opt == '-h' or opt == '--help': 361 | print('Usage: gpiotest.py [--gpio_num ]') 362 | sys.exit() 363 | elif opt == '-g' or opt == '--gpio_num': 364 | if arg == '17': 365 | gpio_num = 17 366 | elif arg == '26': 367 | gpio_num = 26 368 | else: 369 | print('Error: gpio_num must be 17 or 26') 370 | sys.exit() 371 | 372 | try: 373 | #Init curses 374 | myscreen = curses.initscr() 375 | logwindow = myscreen.subwin(5,80,16,0) 376 | msgwindow = myscreen.subwin(1,80,22,0) 377 | termOff() 378 | 379 | #Detect Raspberry Pi model 380 | RaspiModel = getRaspiModel(GPIO.RPI_INFO['REVISION']) 381 | if (RaspiModel == "not supported"): 382 | raise RuntimeError('GPIOTEST does not support this version of Raspberry PI. To add it, visit https://raspi.tv/2015/rpi-gpio-new-feature-gpio-rpi_info-replaces-gpio-rpi_revision') 383 | 384 | #Detect GPIO parameters 385 | #gpio_ch - array of GPIO lines numbers 386 | if gpio_num == 0: 387 | gpio_num = getGpioNum(GPIO.RPI_INFO['REVISION']) 388 | 389 | if (gpio_num == 17): 390 | gpio_ch = [0,1,4,7,8,9,10,11,14,15,17,18,21,22,23,24,25] 391 | else: 392 | gpio_ch = [2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27] 393 | debounce = 200 394 | 395 | #Init vars 396 | # 3 main structures: 397 | # gpio_state stores current states of GPIO pins 398 | # gpio_inout stores where pin configured to be IN or OUT 399 | # gpio_pud stores pin's pullup state 400 | gpio_state = [0 for _ in range(gpio_num)] 401 | gpio_inout = [0 for _ in range(gpio_num)] 402 | gpio_pud = [0 for _ in range(gpio_num)] 403 | on_pause = 0 404 | log = ['' for _ in range(6)] 405 | 406 | #Init GPIO 407 | initGpio(1) 408 | 409 | #Main loop 410 | while True: 411 | MainScreen() 412 | CheckKeys() 413 | time.sleep(0.1) 414 | 415 | except KeyboardInterrupt: 416 | # In case of keyboard interrupt 417 | myscreen.addstr(21,0,"Ctrl-C pressed") 418 | time.sleep(0.5) 419 | GPIO.cleanup() 420 | 421 | except Exception as e: 422 | print(e) 423 | 424 | finally: 425 | # Reset terminal 426 | termOn() 427 | curses.endwin() 428 | --------------------------------------------------------------------------------