Problems with programs not closing that were opened by python - python

I am trying to make a python code that takes the output of an Arduino and starts a Program when a button is pressed. When I press the button on the Arduino it starts the wanted program, but the moment I close the opened the Program it opens itself again and it does that a lot of times. Until I guess, there aren't any more "button High" actuations. Here is my code:
import time
import serial
import subprocess
chonk = serial.Serial('COM5', 9600, timeout=1)
while True:
dataUwU = chonk.readline()
print(dataUwU)
if b'1H' in dataUwU:
subprocess.call("C:\Program Files (x86)\Google\Chrome\Application\chrome.exe")
while b'1H' in dataUwU:
dataUwU = chonk.readline()
print(dataUwU)
if b'2H' in dataUwU:
subprocess.call(r"C:\Users\lastname\AppData\Local\Programs\Microsoft VS Code\Code.exe")
while b'2H' in dataUwU:
dataUwU = chonk.readline()
pass
In the Variable dataUwU saved is the State of the buttons. I dont't understand the weird behavior! Can anyone help? Thanks in advance!!!

You should care about debouncing the button input since most of mechanical buttons (or switches) suffer static noise when you push down or push up them. Even if the button state seem to switch between high and low clearly, the actual waveform show the bounce or fluctuations in a short period as shown below.
So, I guess the Arduino transmits "the button is pressed" packet multiple times even if you pressed the button one time, triggering the subprocess call opening the program multiple times. The second and the subsequent calls may wait until the firstly opened program closed.
If it's correct, your problem can be solved by removing or handling the bounce. To remove the bounce, a capacitor should be connected in parallel with the button to filter the high-frequency noise. If you don't want like hardware, you can modify Arduino sketch to ignore the bounce and figure out the true switch state transition. Please refer to the official debounce example.
Edit) I'm not sure it works, but try this python code instead of modifying the Arduino sketch. You can adjust the DEBOUNCE_DELAY. The shorter the delay, the faster the response, but more vulnerable to the bounce.
import time
import serial
import subprocess
chonk = serial.Serial('COM5', 9600, timeout=1)
t_last_changed = time.time()
last_state = None
button_state = None
DEBOUNCE_DELAY = 0.5
while True:
dataUwU = chonk.readline()
print(dataUwU)
if dataUwU != last_state:
t_last_changed = time.time()
if(time.time() - t_last_changed > DEBOUNCE_DELAY):
if dataUwU != button_state:
button_state = dataUwU
if b'1H' in dataUwU:
subprocess.call("C:\Program Files (x86)\Google\Chrome\Application\chrome.exe")
while b'1H' in dataUwU:
dataUwU = chonk.readline()
print(dataUwU)
if b'2H' in dataUwU:
subprocess.call(r"C:\Users\lastname\AppData\Local\Programs\Microsoft VS Code\Code.exe")
while b'2H' in dataUwU:
dataUwU = chonk.readline()
pass
last_state = dataUwU
Edit2) Even if the python code works, I recommend you to handle the bounce at Arduino because Arduino is better at handling timing-critical tasks than Python code running on your PC.

Related

Cursor input errors when multithreading with python curses librairy

I'm trying to use curses for a class project where we need to use python
The premise is a server/client chat. I was trying to implement it such that the incoming messages from the server would be displayed above the message prompt. I was able to circumvent the python input() function pausing the program with a second thread displaying the messages I decided to use curses to print things at different parts of the screen.
when I tried multi threading with curses I it had a lot of issues with the input cursor. It used displayed the characters that were being input to the program right next to the incoming chat text despite the cursor visually moving back on the screen to the input area
I've isolated the code that's supposed to make this happen:
import threading
from time import sleep
import curses
screen = curses.initscr()
screen.clear()
screen.refresh()
num_rows, num_cols = screen.getmaxyx()
#prints what would be chat to the top of the screen
def thread_delay(thread_name, delay):
i = 0
while i < 10:
#is supposed to save the cursor's location before moving it
global x_location, y_location
x_location, y_location = curses.getsyx()
screen.addstr(0+i, 0, "stuff")
screen.refresh()
#is supposed to restore the cursor to the input location
curses.setsyx(x_location, y_location)
curses.doupdate()
sleep(1)
i+=1
#gets and displays input to the bottom of the screen
def my_raw_input(stdscr, r, c, prompt_string):
curses.echo()
stdscr.addstr(r, c, prompt_string)
stdscr.refresh()
global input
input = stdscr.getstr(r + 1, c, 20)
return # ^^^^ reading input at next line
#threads
t1 = threading.Thread(target = thread_delay, args = ('t1', 1,))
t2 = threading.Thread(target = my_raw_input, args = (screen, num_rows - 4, 0, "type what you want"))
t1.start()
t2.start()
t1.join()
t2.join()
curses.napms(3000)
curses.endwin()
#for debugging
print(input.decode('utf-8'))
Picture of what is happening vs what's supposed to happen:
I guess your class is over, so it might not be useful anymore...
There are several problems....
first problem is that you are not accepting curses text input, and instead using standard input. The curses module, works by moving the cursor around and adding a character, so wherever your last addstr or addch command ends is where the cursor is positioned, and will next add text. So you could possibly move the cursor back to where you want the input to go, after writing to the screen or you could use curses to capture keyboard input.
Secondly using curses in two processes at the same time is probably not a good idea, since it bypasses the GIL. Instead, you could split your code threads into a server and a curses TUI, and share a global send queue and a receive queue from the multiprocessing.Manager to transfer messages.
Then your server thread can send messages from the send_queue with a .get() and receive messages and putting them in the receive_queue with a .put(). Conversely your curses thread can display messages from the recv_queue with a .get() and put a string composed from curses.getch() or curses.getkey() into the send_queue with a .put()
also consider using the curses.wrapper to simplify entering and exiting your curses features. I think you probably didn't use this, because you tried to use curses over multiple processes in which case a wrapper might not work so well.
I'm sure there are other and probably better ways to do it, but this is just one of many. But I hope this helps someone, or encourages someone more experienced to contribute a better answer.

How can I make the Raspberry Pico not automount as USB storage while using Circuit Python

I am using Circuit Python on a Raspberry Pi Pico to give me hardware buttons for keyboard shortcuts. I am using Circuit Python as opposed to MicroPython because it has the USB_HID library.
I don't want the Pico to automatically mount as USB storage when being plugged in. I just want it to act as a HID device. I am aware that I can write a boot.py script in addition to a code.py but I can't find anywhere online what to put in this which would prevent it from mounting as a USB device. I also still want it to mount as USB sometimes (when a button is pressed/GPIO pin is connected) so there is a still a way for me to change the code on the device.
Is this possible? And, if so, what should the boot.py look like to only mount when a certain GPIO pin is connected?
I recently came across a need to do the same that you are looking for, and after a decent rabbit hole, have determined that it can't be done at this time.
https://github.com/adafruit/circuitpython/issues/1015
Looks like the request was opened a few years ago and is still listed as open.
I am not sure if running a pi zero in "gadget" mode can accomplish this or not, but may be worth a look
The code below is what I use. Roughly, it checks to see if a button connected to one of the Pico's IO pins. If the button is pressed when the cable is plugged in, then it mounts as a USB drive. If the button is not pressed, it's not mounted:
import storage
import board, digitalio
# If not pressed, the key will be at +V (due to the pull-up).
# https://learn.adafruit.com/customizing-usb-devices-in-circuitpython/circuitpy-midi-serial#circuitpy-mass-storage-device-3096583-4
button = digitalio.DigitalInOut(board.GP2)
button.direction = digitalio.Direction.INPUT
button.pull = digitalio.Pull.UP
# Disable devices only if button is not pressed.
if button.value:
print(f"boot: button not pressed, disabling drive")
storage.disable_usb_drive()
This was adapted from examples on Adafruit's site
Here's a good HID guide:
https://learn.adafruit.com/circuitpython-essentials/circuitpython-hid-keyboard-and-mouse
Here's the HID example:
import time
import board
import digitalio
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.keycode import Keycode
# A simple neat keyboard demo in CircuitPython
# The pins we'll use, each will have an internal pullup
keypress_pins = [board.A1, board.A2]
# Our array of key objects
key_pin_array = []
# The Keycode sent for each button, will be paired with a control key
keys_pressed = [Keycode.A, "Hello World!\n"]
control_key = Keycode.SHIFT
# The keyboard object!
time.sleep(1) # Sleep for a bit to avoid a race condition on some systems
keyboard = Keyboard(usb_hid.devices)
keyboard_layout = KeyboardLayoutUS(keyboard) # We're in the US :)
# Make all pin objects inputs with pullups
for pin in keypress_pins:
key_pin = digitalio.DigitalInOut(pin)
key_pin.direction = digitalio.Direction.INPUT
key_pin.pull = digitalio.Pull.UP
key_pin_array.append(key_pin)
# For most CircuitPython boards:
led = digitalio.DigitalInOut(board.D13)
# For QT Py M0:
# led = digitalio.DigitalInOut(board.SCK)
led.direction = digitalio.Direction.OUTPUT
print("Waiting for key pin...")
while True:
# Check each pin
for key_pin in key_pin_array:
if not key_pin.value: # Is it grounded?
i = key_pin_array.index(key_pin)
print("Pin #%d is grounded." % i)
# Turn on the red LED
led.value = True
while not key_pin.value:
pass # Wait for it to be ungrounded!
# "Type" the Keycode or string
key = keys_pressed[i] # Get the corresponding Keycode or string
if isinstance(key, str): # If it's a string...
keyboard_layout.write(key) # ...Print the string
else: # If it's not a string...
keyboard.press(control_key, key) # "Press"...
keyboard.release_all() # ..."Release"!
# Turn off the red LED
led.value = False
time.sleep(0.01) ```
The example only prints Hello World, so it's not very useful, but it's a good base to mod.
i try this and it work
https://learn.adafruit.com/circuitpython-essentials/circuitpython-storage
it not write to code.pi but in boot.py you have to create the file first.

Python - scheduler blocks interrupt

I am using a system based on a raspberry to control some stuff. At the moment I am just testing it with turning a led on and off.
My plan is: Press a button to open a valve. Press the button again to close it - but if the button is not pressed, close it after a set time. My current script is as follows: (I know that this will not turn the led off on the second press)
import RPi.GPIO as GPIO # Import Raspberry Pi GPIO library
import time,sched
s = sched.scheduler(time.time, time.sleep)
def button_callback(channel):
print("Button was pushed!")
print(time.time())
GPIO.output(18,GPIO.HIGH)
s.enter(10, 1, turnoff,argument='')
s.run()
def turnoff():
print "LED off"
print(time.time())
GPIO.output(18,GPIO.LOW)
btpin=22
ledpin=18
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(btpin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(ledpin,GPIO.OUT)
GPIO.add_event_detect(btpin,GPIO.RISING,callback=button_callback)
message = input("Press enter to quit\n\n")
GPIO.cleanup()
If I press the button and then leaves things alone, the led will turn off after 10 secs. But, if I press the button again immideately, nothing happens before the scheduler has finished, then a new press is registered. I had expected that the scheduler was spun of in the background so that when I pressed the button again, the callback would have ran again so I would have gotten the "Button was pushed" message (and everything happening afterwards would not have had any effect as GPIO 18 already was high and the scheduled call to turnoff would have happened after turnoff already had run.
Is it possible to use the sched library to do what I want or do I have to use some other techniques? I know I can either do it the simple way, by looping looking for a pressed button rather than registering a callback, or probably a more complicated way by creating a new thread that will pull the GPIO down after a given time - but is there something I have not understood in sched or is there some other library that gives me what I want - a way to tell python do do something a bit in the future without interfering with what else is going on.
(I do not need a very accurate timing - and also, this is just a part of what I intend to make a more complex control system, I manage to do exactly what I want using an arduino, but that will limit the further development)
Thanks to the tip from #stovfl, I rewrote the first part of my code:
import time,threading
def button_callback(channel):
pin=18
print("Button was pushed!")
print(time.time())
GPIO.output(pin,GPIO.HIGH)
t = threading.Timer(10.0, turnoff)
t.start()
and it works just like I wanted

Non-blocking infinite loop

I have a Raspberry pi with a Sense hat. I've made a binary clock that I want to display and keep updated on the Sense hat's display. However, I want the ability to toggle the clock on and off with joystick middle. Everything's working fine, apart from my clock's update-loop blocking any new input once it's started.
from sense_hat import SenseHat
from signal import pause
def show_clock():
# clock-logic
def pushed_middle(event):
while 1:
show_clock()
sense = SenseHat()
sense.stick.direction_middle = pushed_middle
pause
I've been thinking about how to solve this. How to allow the script/clock to keep running and still accept new actions from the joystick. But once the while-loop starts, I'm stuck. I'm not sure what to google for. I've started looking into async/await, but that seem to be a Python 3.5+ feature, and my pi only has 2.7.9/3.4.2(I just sudo apt-get update/upgrade-ed). I've also tried moving the loop around in the program, but it's blocking everything no matter where I place it.
Is it a non-blocking (infinite) loop I'm looking for?
Is this what a game-/event-loop is?
Can I solve this with out using multiple threads(just curious, not a limitation if it's a must)?
Is this a general problem in "designing" infinite loops?
Can I approach this as a (reverse?) race condition? I was thinking about maybe using a semaphore as some kind of tool to not block, but I'm not sure.
I solved it by using a global variable:
from sense_hat import SenseHat
from signal import pause
def show_clock():
global clock_is_on
while clock_is_on: # clock-loop
# clock-logic
# ...
events = sense.stick.get_events()
for event in events:
if event.direction == "middle" and event.action == "pressed":
clock_is_on = False
time.sleep(1) # only need to update clock once every second
def pushed_middle(event):
if not clock_is_on:
clock_is_on = True
show_clock()
sense = SenseHat()
clock_is_on = False
sense.stick.direction_middle = pushed_middle
pause()

How to stop a warning dialog from halting execution of a Python program that's controlling it?

Using Win32GUI and Watsup, I'm writing a bit of Python code to automate a search across a database that is accessed through a program that doesn't come with an interface for it. As such, I can take a string from a list and then input it into the search box and press 'lookup'.
However, when the search returns more than 1000 results, the program throws a warning dialog --which is simply a notification of the number of results--which halts the execution of the Python code. I can't get the code to progress past the line where it presses lookup.
At a guess, this would be because it doesn't expect a window or know how to handle a warning--but I don't either, other than manually accepting it. Below is the relevent sample of code, though it's probably not very enlightening. After "clickButton(LookupButton)", the execution halts.
LookupButtonlocation = elemstring.find("Lookup", AuthNameFieldlocation) - 15
#Use Regex search to find handles
number_regex = re.compile(';(\d+);')
AuthNameEdit = int(number_regex.search(elemstring[AuthNameFieldlocation:]).group(1))
LookupButton = int(number_regex.search(elemstring[LookupButtonlocation:]).group(1))
#Input new Author into Edit Field
setEditText(AuthNameEdit, "John Campbell")
#Click lookup button
clickButton(LookupButton)
I'm not a WATSUP user, but I do something very similar using pywinauto - in my case I'm running a number of automated tests that open various 3rd party programs that, in a similar way, throw up inconvenient warning dialogs. It's a bit difficult to deal with dialogs that you don't know about, however if you do know which dialogs appear, but not when they appear, you can start a thread to just deal with those pop-ups. The following is a simple example from what I'm doing, and uses pywinauto but you could adapt the approach for WATSUP:
import time
import threading
class ClearPopupThread(threading.Thread):
def __init__(self, window_name, button_name, quit_event):
threading.Thread.__init__(self)
self.quit_event = quit_event
self.window_name = window_name
self.button_name = button_name
def run(self):
from pywinauto import application, findwindows
while True:
try:
handles = findwindows.find_windows(title=self.window_name)
except findwindows.WindowNotFoundError:
pass #Just do nothing if the pop-up dialog was not found
else: #The window was found, so click the button
for hwnd in handles:
app = application.Application()
app.Connect(handle=hwnd)
popup = app[self.window_name]
button = getattr(popup, self.button_name)
button.Click()
if self.quit_event.is_set():
break
time.sleep(1) #should help reduce cpu load a little for this thread
Essentially this thread is just an infinite loop that looks for a pop-up window by name, and if it finds it, it clicks on a button to close the window. If you have many pop-up windows you can open one thread per popup (bug that's not overly efficient, though). Because it's an infinite loop, I have the thread looking to see if an event is set, to allow me to stop the thread from my main program. So, in the main program I do something like this:
#Start the thread
quit_event = threading.Event()
mythread = ClearPopupThread('Window Popup Title', 'Yes button', quit_event)
# ...
# My program does it's thing here
# ...
# When my program is done I need to end the thread
quit_event.set()
This is not necessarily the only way to deal with your issue, but is a way that's worked for me. Sorry I can't really help you much with dealing with WATSUP (I always found pywinauto a bit easier to use), but I noticed on the WATSUP homepage (http://www.tizmoi.net/watsup/intro.html), Example 2 does something similar without using threads, i.e., looks for a named window and clicks a specific button on that window.

Categories

Resources