Python Code for Controller Raspberry Pi
Code for the Moto-Mast Raspberry Pi Controller is comprised of three modules plus a launching script. The main program, MotoMast2.py runs in the forground process. There are two background processes which are launched first. These are RPMZero.py and SW-Shutdown3.py. All of these are loaded in the correct order by the script Launcher.sh. There is also a file named ids.json which contains the count for the height of the mast in "clicks" on the winch drive shaft. If the mast is docked, this file just contains the number 0.
All of these files are available on the Download page. Contact KD6X if you would like to receive a complete image of the 8 GB MicroSD card containing the Raspbian OS and integrated Moto-Mast program files.
Code for main program MotoMast2
1 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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 | #!/usr/bin/python #-------------------------------------- # ___ ___ _ ____ # / _ \/ _ \(_) __/__ __ __ # / , _/ ___/ /\ \/ _ \/ // / # /_/|_/_/ /_/___/ .__/\_, / # /_/ /___/ # # lcd_16x2.py # 16x2 LCD Test Script # # Author : Matt Hawkins # Date : 06/04/2015 # # http://www.raspberrypi-spy.co.uk/ # # Modified and extended for the MotoMast Control # By Courtney E. Krehbiel, October 2015 # # Ver. 1.02 change in Dec. 2015 to modify rpmzero.py "SmallSleep" delay variable to 3 from 5 # Ver. 1.10 change on 2/23/2016 to add docking zero function to rpmzero.py and changes to how nCount is handled in MotoMast2. # #-------------------------------------- # The wiring for the LCD is as follows: # 1 : GND # 2 : 5V # 3 : Contrast (0-5V)* # 4 : RS (Register Select) # 5 : R/W (Read Write) - GROUND THIS PIN # 6 : Enable or Strobe # 7 : Data Bit 0 - NOT USED # 8 : Data Bit 1 - NOT USED # 9 : Data Bit 2 - NOT USED # 10: Data Bit 3 - NOT USED # 11: Data Bit 4 # 12: Data Bit 5 # 13: Data Bit 6 # 14: Data Bit 7 # 15: LCD Backlight +5V** # 16: LCD Backlight GND #import import RPi.GPIO as GPIO import time import json # Define GPIO to LCD mapping LCD_RS = 14 LCD_E = 15 LCD_D4 = 17 LCD_D5 = 18 LCD_D6 = 27 LCD_D7 = 22 # Define some device constants LCD_WIDTH = 16 # Maximum characters per line LCD_CHR = True LCD_CMD = False LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line # Timing constants E_PULSE = 0.0005 E_DELAY = 0.0005 #CEK Global Variables for MotoMast BtnPin = 23 # pin 16 for pushbutton, GPIO 23 (GPIO 25, pin 22 on blown new board) SwUpDown = 24 # pin 18 for up/down switch, GPIO 24 (GPIO 16, pin 36 on blown new board) # BtnPin = 25 # pin 16 for pushbutton, GPIO 23 (GPIO 25, pin 22 on blown new board) # SwUpDown = 16 # pin 18 for up/down switch, GPIO 24 (GPIO 16, pin 36 on blown new board) sHeightLabel = "Height Ft: " sRPM = "RPM: 0.00" nRPM = 0 Height = 0 HeightFeet = 0 sHeightFeet = "" StartTime = 0 def setup(): # Setup program block GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) # Use BCM GPIO numbers GPIO.setup(LCD_E, GPIO.OUT) # E GPIO.setup(LCD_RS, GPIO.OUT) # RS GPIO.setup(LCD_D4, GPIO.OUT) # DB4 GPIO.setup(LCD_D5, GPIO.OUT) # DB5 GPIO.setup(LCD_D6, GPIO.OUT) # DB6 GPIO.setup(LCD_D7, GPIO.OUT) # DB7 # Initialise display lcd_init() global sRPM global StartTime GPIO.setup(BtnPin, GPIO.IN, pull_up_down=GPIO.PUD_UP) #RPM indicator switch GPIO.setup(SwUpDown, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Up/Down indicator switch lcd_string('KD6X Moto-Mast', LCD_LINE_1) # Provide display initialization info lcd_string('Firmware v1.10', LCD_LINE_2) time.sleep(4) # 4 second delay displayheight() # Initial display of data on display lcd_string(sRPM, LCD_LINE_2) # Display RPM: 0.00 from initial string variable sRPM = 'RPM: ' # Reset the string for when running to eliminate extra zeros StartTime = time.time() # Set the clock for RPM timing def displayheight(): global Height global HeightFeet global sHeightFeet with open('ids.json', 'r') as fp: # Get current position count from disk nCount = json.load(fp) fp.close() # print 'Ncount: ', str(nCount) Height = 120 + (nCount * 1.026) # Mast goes up 1.026 inches per revolution of winch shaft HeightFeet = Height / 12 HeightFeet = round(HeightFeet,2) sHeightFeet = str("%.2f" % HeightFeet) # Convert to feet, round to one decimal point, and then string sNewHeightString = ''.join([sHeightLabel, sHeightFeet]) lcd_string(sNewHeightString, LCD_LINE_1) def displayrpm(): global nRPM global StartTime global RPMStartUp NowTime = time.time() nRPM = 60 / (NowTime - StartTime) # print 'Start:', str(StartTime), 'Now:', str(NowTime), 'RPM:', str(nRPM) nRPM = round(nRPM,2) sActualRPM = str("%.2f" % nRPM) sNewRPMString = ''.join([sRPM, sActualRPM]) lcd_string(sNewRPMString, LCD_LINE_2) StartTime = NowTime # Setup start time for accurate RPM capture on next revolution. First one will be pretty close to 0. def swcount(ev=None): #This is the routine that runs everytime the winch shaft rotates on revolution # print 'Button pressed: ', str(nCount) with open('ids.json', 'r') as fp: # Get current position count from disk nCount = json.load(fp) fp.close() if GPIO.input(SwUpDown) == GPIO.HIGH: nCount = nCount + 1 # Increment or decrement the position counter else: nCount = nCount - 1 with open('ids.json', 'w') as fp: # Write current count to disk just in case motor is stopped json.dump(nCount, fp) fp.close() displayheight() # Do the math and formatting to output the data displayrpm() # A few more lines from CEK to set the overall loop def loop(): while True: GPIO.wait_for_edge(BtnPin, GPIO.FALLING, bouncetime=200) #Triggers swcount function on debounced falling edge of count pulse. swcount() def destroy(): lcd_byte(0x01, LCD_CMD) #Clear display lcd_string("Goodbye!",LCD_LINE_1) GPIO.cleanup() #Release resources # End of CEK code def lcd_init(): # Initialise display lcd_byte(0x33,LCD_CMD) # 110011 Initialise lcd_byte(0x32,LCD_CMD) # 110010 Initialise lcd_byte(0x06,LCD_CMD) # 000110 Cursor move direction lcd_byte(0x0C,LCD_CMD) # 001100 Display On,Cursor Off, Blink Off lcd_byte(0x28,LCD_CMD) # 101000 Data length, number of lines, font size lcd_byte(0x01,LCD_CMD) # 000001 Clear display time.sleep(E_DELAY) def lcd_byte(bits, mode): # Send byte to data pins # bits = data # mode = True for character # False for command GPIO.output(LCD_RS, mode) # RS # High bits GPIO.output(LCD_D4, False) GPIO.output(LCD_D5, False) GPIO.output(LCD_D6, False) GPIO.output(LCD_D7, False) if bits&0x10==0x10: GPIO.output(LCD_D4, True) if bits&0x20==0x20: GPIO.output(LCD_D5, True) if bits&0x40==0x40: GPIO.output(LCD_D6, True) if bits&0x80==0x80: GPIO.output(LCD_D7, True) # Toggle 'Enable' pin lcd_toggle_enable() # Low bits GPIO.output(LCD_D4, False) GPIO.output(LCD_D5, False) GPIO.output(LCD_D6, False) GPIO.output(LCD_D7, False) if bits&0x01==0x01: GPIO.output(LCD_D4, True) if bits&0x02==0x02: GPIO.output(LCD_D5, True) if bits&0x04==0x04: GPIO.output(LCD_D6, True) if bits&0x08==0x08: GPIO.output(LCD_D7, True) # Toggle 'Enable' pin lcd_toggle_enable() def lcd_toggle_enable(): # Toggle enable time.sleep(E_DELAY) GPIO.output(LCD_E, True) time.sleep(E_PULSE) GPIO.output(LCD_E, False) time.sleep(E_DELAY) def lcd_string(message,line): # Send string to display message = message.ljust(LCD_WIDTH," ") lcd_byte(line, LCD_CMD) for i in range(LCD_WIDTH): lcd_byte(ord(message[i]),LCD_CHR) if __name__ == '__main__': setup() try: loop() except KeyboardInterrupt: #When 'Ctrl-C' is pressed, the child program destroy() will be executed. pass finally: destroy() |
Code for background module SW-Shutdown3
1 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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | #!/bin/python # # # Written by Courtney Krehbiel # October 2015 # # # Purpose is to run as a background program and monitor GPIO 21 to see if it is grounded. # If high to low (ground) detected it will perform an orderly shutdown of the Raspberry Pi # # import RPi.GPIO as GPIO import os import sys import time switchpin = 21 # Using GPIO 21, physical pin 40 since it's close to a ground pin. GPIO.setmode(GPIO.BCM) # Using BCM numbers, not physical pin numbers GPIO.setup(switchpin, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Set as input, and pulled high by default GPIO.wait_for_edge(switchpin, GPIO.FALLING, bouncetime=200) # Program will stop at this line and wait for falling edge, if ever. # If program ever gets here, we're shutting down. # Define GPIO to LCD mapping LCD_RS = 14 LCD_E = 15 LCD_D4 = 17 LCD_D5 = 18 LCD_D6 = 27 LCD_D7 = 22 # Define some device constants LCD_WIDTH = 16 # Maximum characters per line LCD_CHR = True LCD_CMD = False LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line # Timing constants E_PULSE = 0.0005 E_DELAY = 0.0005 GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) # Use BCM GPIO numbers GPIO.setup(LCD_E, GPIO.OUT) # E GPIO.setup(LCD_RS, GPIO.OUT) # RS GPIO.setup(LCD_D4, GPIO.OUT) # DB4 GPIO.setup(LCD_D5, GPIO.OUT) # DB5 GPIO.setup(LCD_D6, GPIO.OUT) # DB6 GPIO.setup(LCD_D7, GPIO.OUT) # DB7 def lcd_init(): # Initialise display lcd_byte(0x33,LCD_CMD) # 110011 Initialise lcd_byte(0x32,LCD_CMD) # 110010 Initialise lcd_byte(0x06,LCD_CMD) # 000110 Cursor move direction lcd_byte(0x0C,LCD_CMD) # 001100 Display On,Cursor Off, Blink Off lcd_byte(0x28,LCD_CMD) # 101000 Data length, number of lines, font size lcd_byte(0x01,LCD_CMD) # 000001 Clear display time.sleep(E_DELAY) def lcd_byte(bits, mode): # Send byte to data pins # bits = data # mode = True for character # False for command GPIO.output(LCD_RS, mode) # RS # High bits GPIO.output(LCD_D4, False) GPIO.output(LCD_D5, False) GPIO.output(LCD_D6, False) GPIO.output(LCD_D7, False) if bits&0x10==0x10: GPIO.output(LCD_D4, True) if bits&0x20==0x20: GPIO.output(LCD_D5, True) if bits&0x40==0x40: GPIO.output(LCD_D6, True) if bits&0x80==0x80: GPIO.output(LCD_D7, True) # Toggle 'Enable' pin lcd_toggle_enable() # Low bits GPIO.output(LCD_D4, False) GPIO.output(LCD_D5, False) GPIO.output(LCD_D6, False) GPIO.output(LCD_D7, False) if bits&0x01==0x01: GPIO.output(LCD_D4, True) if bits&0x02==0x02: GPIO.output(LCD_D5, True) if bits&0x04==0x04: GPIO.output(LCD_D6, True) if bits&0x08==0x08: GPIO.output(LCD_D7, True) # Toggle 'Enable' pin lcd_toggle_enable() def lcd_toggle_enable(): # Toggle enable time.sleep(E_DELAY) GPIO.output(LCD_E, True) time.sleep(E_PULSE) GPIO.output(LCD_E, False) time.sleep(E_DELAY) def lcd_string(message,line): # Send string to display message = message.ljust(LCD_WIDTH," ") lcd_byte(line, LCD_CMD) for i in range(LCD_WIDTH): lcd_byte(ord(message[i]),LCD_CHR) def haltsystem(ev=None): lcd_string("Halting RPi... ", LCD_LINE_1) lcd_string("Pse wait 30 sec.", LCD_LINE_2) os.system("sudo shutdown -h now") # Send command to shutdown the system # print 'System being halted' sys.exit(0) # Initialise display lcd_init() GPIO.remove_event_detect(switchpin) # Send some test lcd_byte(0x01,LCD_CMD) # 000001 Clear display lcd_string("REBOOT in 15 sec",LCD_LINE_1) lcd_string("Push agn to HALT",LCD_LINE_2) time.sleep(2) # Debounce the keypress to make sure there are two distinct pushes. GPIO.add_event_detect(switchpin, GPIO.FALLING) # wait for any activity CountDown = 1500 while CountDown > 0: if GPIO.event_detected(switchpin): haltsystem() time.sleep(0.01) CountDown = CountDown - 1 # Button not pressed 2nd time, so will reboot instead lcd_string("Rebooting RPi...", LCD_LINE_1) lcd_string("Pse wait 30 sec.", LCD_LINE_2) #print 'System being rebooted' #time.sleep(5) #lcd_init() # Clean up display #GPIO.cleanup() # Tidy up before exit os.system("sudo reboot") # Send command to reboot the system # To run as a background program which takes very few resources, use the command: sudo python sw-shutdown.py & |
Code for background module RPMZero
1 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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | #!/usr/bin/python #-------------------------------------- # RPM Zero. # A program meant to run in the background, and # reset the RPM display to 0 RPM if there haven't been # any changes in about 5 seconds. # # Modified and extended for the MotoMast Control # By Courtney E. Krehbiel, October 2015 # #-------------------------------------- # The wiring for the LCD is as follows: # 1 : GND # 2 : 5V # 3 : Contrast (0-5V)* # 4 : RS (Register Select) # 5 : R/W (Read Write) - GROUND THIS PIN # 6 : Enable or Strobe # 7 : Data Bit 0 - NOT USED # 8 : Data Bit 1 - NOT USED # 9 : Data Bit 2 - NOT USED # 10: Data Bit 3 - NOT USED # 11: Data Bit 4 # 12: Data Bit 5 # 13: Data Bit 6 # 14: Data Bit 7 # 15: LCD Backlight +5V** # 16: LCD Backlight GND #import import RPi.GPIO as GPIO import time import json # Define GPIO to LCD mapping LCD_RS = 14 LCD_E = 15 LCD_D4 = 17 LCD_D5 = 18 LCD_D6 = 27 LCD_D7 = 22 # Define some device constants LCD_WIDTH = 16 # Maximum characters per line LCD_CHR = True LCD_CMD = False LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line # Timing constants E_PULSE = 0.0005 E_DELAY = 0.0005 #CEK Global Variables for MotoMast sRPM = "RPM: 0.00" SavedHeight = 0 JustClearedRPM = 0 MovementDetected = 0 SmallSleep = 3 def setup(): # Setup program block GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) # Use BCM GPIO numbers GPIO.setup(LCD_E, GPIO.OUT) # E GPIO.setup(LCD_RS, GPIO.OUT) # RS GPIO.setup(LCD_D4, GPIO.OUT) # DB4 GPIO.setup(LCD_D5, GPIO.OUT) # DB5 GPIO.setup(LCD_D6, GPIO.OUT) # DB6 GPIO.setup(LCD_D7, GPIO.OUT) # DB7 # Initialise display # lcd_init() with open('ids.json', "r") as fp: # Get current position count from disk SavedHeight = json.load(fp) fp.close() def swcount(ev=None): #This is the routine that runs and checks to see if there's activity in ids.json global SavedHeight global MovementDetected with open('ids.json', "r") as fp: # Get current position count from disk HeightNow = json.load(fp) fp.close() if HeightNow == SavedHeight: # No movement has occurred. MovementDetected = 0 time.sleep(SmallSleep) # No sense checking every millisecond pass else: MovementDetected = 1 # ids.json has changed, so movement is occurring. SavedHeight = HeightNow # Setup to see if it keeps moving time.sleep(SmallSleep) # wait 5 seconds with open('ids.json', "r") as fp: # Get current position count from disk HeightNow = json.load(fp) fp.close() if HeightNow == SavedHeight: # No movement for 5 seconds, so let's set RPM display to 0 lcd_string(sRPM, LCD_LINE_2) # Let's also check to see if we should reset height at 10 feet to eliminate small click errors. if abs(HeightNow) <= 3: with open ('ids.json', 'w') as fp: # If we are within 3 revs of bottom, reset height to 0 which is 10 foot dock height. json.dump(0, fp) fp.close() lcd_string("Height Ft: 10.00", LCD_LINE_1) # Write this out to the display too HeightNow = 0 SavedHeight = 0 # Update these guys too else: pass # Must still be moving # A few more lines from CEK to set the overall loop def loop(): while True: swcount() def destroy(): lcd_byte(0x01, LCD_CMD) #Clear display lcd_string("Goodbye!",LCD_LINE_1) GPIO.cleanup() #Release resources # End of CEK code def lcd_init(): # Initialise display lcd_byte(0x33,LCD_CMD) # 110011 Initialise lcd_byte(0x32,LCD_CMD) # 110010 Initialise lcd_byte(0x06,LCD_CMD) # 000110 Cursor move direction lcd_byte(0x0C,LCD_CMD) # 001100 Display On,Cursor Off, Blink Off lcd_byte(0x28,LCD_CMD) # 101000 Data length, number of lines, font size lcd_byte(0x01,LCD_CMD) # 000001 Clear display time.sleep(E_DELAY) def lcd_byte(bits, mode): # Send byte to data pins # bits = data # mode = True for character # False for command GPIO.output(LCD_RS, mode) # RS # High bits GPIO.output(LCD_D4, False) GPIO.output(LCD_D5, False) GPIO.output(LCD_D6, False) GPIO.output(LCD_D7, False) if bits&0x10==0x10: GPIO.output(LCD_D4, True) if bits&0x20==0x20: GPIO.output(LCD_D5, True) if bits&0x40==0x40: GPIO.output(LCD_D6, True) if bits&0x80==0x80: GPIO.output(LCD_D7, True) # Toggle 'Enable' pin lcd_toggle_enable() # Low bits GPIO.output(LCD_D4, False) GPIO.output(LCD_D5, False) GPIO.output(LCD_D6, False) GPIO.output(LCD_D7, False) if bits&0x01==0x01: GPIO.output(LCD_D4, True) if bits&0x02==0x02: GPIO.output(LCD_D5, True) if bits&0x04==0x04: GPIO.output(LCD_D6, True) if bits&0x08==0x08: GPIO.output(LCD_D7, True) # Toggle 'Enable' pin lcd_toggle_enable() def lcd_toggle_enable(): # Toggle enable time.sleep(E_DELAY) GPIO.output(LCD_E, True) time.sleep(E_PULSE) GPIO.output(LCD_E, False) time.sleep(E_DELAY) def lcd_string(message,line): # Send string to display message = message.ljust(LCD_WIDTH," ") lcd_byte(line, LCD_CMD) for i in range(LCD_WIDTH): lcd_byte(ord(message[i]),LCD_CHR) if __name__ == '__main__': setup() try: loop() except KeyboardInterrupt: #When 'Ctrl-C' is pressed, the child program destroy() will be executed. pass finally: destroy() |
Code for launching script, Launcher.sh
1 2 3 4 5 6 7 8 9 10 11 | #!/bin/sh # launcher.sh # Navigate to home directory, then to this directory, then execute python script, then back home. cd / cd /home/pi/code sudo python sw-shutdown3.py & sudo python rpmzero.py & sudo python motomast2.py cd / cd home/pi/code |