I'm trying to create a Python program that takes input from an Arduino to change the monitor's input port. Currently everything works fine, but when the program runs, the cursor on Windows shows a spinning circle constantly. It's super annoying, and I found out that it is related to the fragment of code below.
# Check serial monitor to see if button has been pressed
while True:
try:
data = arduino.readline()[:-2]
if data:
... Some irrelevant code ...
# Check if input has changed unexpectedly
elif (subprocess.call(r'ControlMyMonitor.exe /GetValue "\\.\DISPLAY1\Monitor0" 60', startupinfo=si) == 15 and current == 17) or (subprocess.Popen(r'ControlMyMonitor.exe /GetValue "\\.\DISPLAY1\Monitor0" 60', startupinfo=si) == 17 and current == 15):
print("Detected monitor switch")
if current == HDMI:
arduino.write('1'.encode())
current = DP
elif current == DP:
arduino.write('2'.encode())
current = HDMI
except:
sys.exit("Communication to Arduino was interrupted")
I'm calling the program ControlMyMonitor.exe multiple times every second to validate the input port value, which is causing the cursor to spin forever when the program is running. Is there a way to prevent this, or should I give up on checking if the monitor input has changed unexpectedly (i.e. no device connected to HDMI port after switching manually, and monitor automatically switches back to DP)?
I ended up solving this by cutting out the middleman. I added a file called moncontrol.py which is pasted below the original fragment of code with my changes. I added the function "get_monitor_input" which returns an integer value of the monitor selected. This way, the code can stay relatively the same.
from datetime import datetime
from ctypes import windll, WinError
import time, os, sys, serial, moncontrol
while True:
handles = []
for handle in moncontrol.iter_physical_monitors(False):
handles.append(handle)
try:
data = arduino.readline()[:-2]
currMon = moncontrol.get_monitor_input(handles[monitor], 0x60)
if data:
... irrelevant code ...
# Check if input has changed unexpectedly
elif (currMon == DP and current == HDMI) or (currMon == HDMI and current == DP):
if current == HDMI:
arduino.write('1'.encode())
current = DP
elif current == DP:
arduino.write('2'.encode())
current = HDMI
else:
destroyHandles(handles)
except serial.SerialException:
destroyHandles(handles)
createLog()
destroyHandles(handles)
def destroyHandles(handles):
for handle in handles:
windll.dxva2.DestroyPhysicalMonitor(handle)
moncontrol.py:
from ctypes import windll, byref, Structure, WinError, POINTER, WINFUNCTYPE
from ctypes.wintypes import BOOL, HMONITOR, HDC, RECT, LPARAM, DWORD, BYTE, WCHAR, HANDLE
_MONITORENUMPROC = WINFUNCTYPE(BOOL, HMONITOR, HDC, POINTER(RECT), LPARAM)
class _PHYSICAL_MONITOR(Structure):
_fields_ = [('handle', HANDLE),
('description', WCHAR * 128)]
def iter_physical_monitors(close_handles=True):
"""Iterates physical monitors.
The handles are closed automatically whenever the iterator is advanced.
This means that the iterator should always be fully exhausted!
If you want to keep handles e.g. because you need to store all of them and
use them later, set `close_handles` to False and close them manually."""
def callback(hmonitor, hdc, lprect, lparam):
monitors.append(HMONITOR(hmonitor))
return True
monitors = []
if not windll.user32.EnumDisplayMonitors(None, None, _MONITORENUMPROC(callback), None):
raise WinError('EnumDisplayMonitors failed')
for monitor in monitors:
# Get physical monitor count
count = DWORD()
windll.dxva2.GetNumberOfPhysicalMonitorsFromHMONITOR(monitor, byref(count))
# Get physical monitor handles
physical_array = (_PHYSICAL_MONITOR * count.value)()
windll.dxva2.GetPhysicalMonitorsFromHMONITOR(monitor, count.value, physical_array)
for physical in physical_array:
yield physical.handle
if close_handles:
if not windll.dxva2.DestroyPhysicalMonitor(physical.handle):
raise WinError()
def get_monitor_input(monitor, code):
current = DWORD()
windll.dxva2.GetVCPFeatureAndVCPFeatureReply(monitor, code, None, byref(current), None)
return current.value
def set_vcp_feature(monitor, code, value):
"""Sends a DDC command to the specified monitor.
See this link for a list of commands:
ftp://ftp.cis.nctu.edu.tw/pub/csie/Software/X11/private/VeSaSpEcS/VESA_Document_Center_Monitor_Interface/mccsV3.pdf
"""
if not windll.dxva2.SetVCPFeature(HANDLE(monitor), BYTE(code), DWORD(value)):
raise WinError()
Related
so I've been thinking about this for a couple days now and I cant figure it out, I've searched around but couldn't find the answer I was looking for, so any help would be greatly appreciated.
Essentially what I am trying to do is call a method on a group of objects in my main thread from a separate thread, just once after 2 seconds and then the thread can exit, I'm just using threading as a way of creating a non-blocking 2 second pause (if there are other ways of accomplishing this please let me know.
So I have a pyqtplot graph/plot that updates from a websocket stream and the gui can only be updated from the thread that starts it (the main one).
What happens is I open a websocket stream fill up a buffer for about 2 seconds, make an REST request, apply the updates from the buffer to the data from the REST request and then update the data/plot as new messages come in. Now the issue is I can't figure out how to create a non blocking 2 second pause in the main thread without creating a child thread. If I create a child thread and pass the object that contains the dictionary I want to update after 2 seconds, I get issues regarding updating the plot from a different thread. So what I THINK is happening is when that new spawned thread is spawned the reference to the object I want to update is actually the object itself, or the data (dictionary) containing the update data is now in a different thread as the gui and that causes issues.
open websocket --> start filling buffer --> wait 2 seconds --> REST request --> apply updates from buffer to REST data --> update data as new websocket updates/messages come in.
Unfortunately the websocket and gui only start when you run pg.exec() and you can't break them up to start individually, you create them and then start them together (or at least I have failed to find a way to start them individually, alternatively I also tried using a separate library to handle websockets however this requires starting a thread for incoming messages as well)
This is the minimum reproducible example, sorry it's pretty long but I couldn't really break it down anymore without removing required functionality as well as preserving context:
import json
import importlib
from requests.api import get
import functools
import time
import threading
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore
QtWebSockets = importlib.import_module(pg.Qt.QT_LIB + '.QtWebSockets')
class coin():
def __init__(self):
self.orderBook = {'bids':{}, 'asks':{}}
self.SnapShotRecieved = False
self.last_uID = 0
self.ordBookBuff = []
self.pltwgt = pg.PlotWidget()
self.pltwgt.show()
self.bidBar = pg.BarGraphItem(x=[0], height=[1], width= 1, brush=(25,25,255,125), pen=(0,0,0,0))
self.askBar = pg.BarGraphItem(x=[1], height=[1], width= 1, brush=(255,25,25,125), pen=(0,0,0,0))
self.pltwgt.addItem(self.bidBar)
self.pltwgt.addItem(self.askBar)
def updateOrderBook(self, message):
for side in ['a','b']:
bookSide = 'bids' if side == 'b' else 'asks'
for update in message[side]:
if float(update[1]) == 0:
try:
del self.orderBook[bookSide][float(update[0])]
except:
pass
else:
self.orderBook[bookSide].update({float(update[0]): float(update[1])})
while len(self.orderBook[bookSide]) > 1000:
del self.orderBook[bookSide][(min(self.orderBook['bids'], key=self.orderBook['bids'].get)) if side == 'b' else (max(self.orderBook['asks'], key=self.orderBook['asks'].get))]
if self.SnapShotRecieved == True:
self.bidBar.setOpts(x0=self.orderBook['bids'].keys(), height=self.orderBook['bids'].values(), width=1 )
self.askBar.setOpts(x0=self.orderBook['asks'].keys(), height=self.orderBook['asks'].values(), width=1 )
def getOrderBookSnapshot(self):
orderBookEncoded = get('https://api.binance.com/api/v3/depth?symbol=BTCUSDT&limit=1000')
if orderBookEncoded.ok:
rawOrderBook = orderBookEncoded.json()
orderBook = {'bids':{}, 'asks':{}}
for orders in rawOrderBook['bids']:
orderBook['bids'].update({float(orders[0]): float(orders[1])})
for orders in rawOrderBook['asks']:
orderBook['asks'].update({float(orders[0]): float(orders[1])})
last_uID = rawOrderBook['lastUpdateId']
while self.ordBookBuff[0]['u'] <= last_uID:
del self.ordBookBuff[0]
if len(self.ordBookBuff) == 0:
break
if len(self.ordBookBuff) >= 1 :
for eachUpdate in self.ordBookBuff:
self.last_uID = eachUpdate['u']
self.updateOrderBook(eachUpdate)
self.ordBookBuff = []
self.SnapShotRecieved = True
else:
print('Error retieving order book.') #RESTfull request failed
def on_text_message(message, refObj):
messaged = json.loads(message)
if refObj.SnapShotRecieved == False:
refObj.ordBookBuff.append(messaged)
else:
refObj.updateOrderBook(messaged)
def delay(myObj):
time.sleep(2)
myObj.getOrderBookSnapshot()
def main():
pg.mkQApp()
refObj = coin()
websock = QtWebSockets.QWebSocket()
websock.connected.connect(lambda : print('connected'))
websock.disconnected.connect(lambda : print('disconnected'))
websock.error.connect(lambda e : print('error', e))
websock.textMessageReceived.connect(functools.partial(on_text_message, refObj=refObj))
url = QtCore.QUrl("wss://stream.binance.com:9443/ws/btcusdt#depth#1000ms")
websock.open(url)
getorderbook = threading.Thread(target = delay, args=(refObj,), daemon=True) #, args = (lambda : websocketThreadExitFlag,)
getorderbook.start()
pg.exec()
if __name__ == "__main__":
main()
The Qt documentation (https://doc.qt.io/qtforpython-5/PySide2/QtMultimedia/QAudioBuffer.html) says that we should read the buffer from QAudioProbe like this:
// With a 16bit sample buffer:
quint16 *data = buffer->data<quint16>(); // May cause deep copy
This is C++, but I need to write this in Python.
I am not sure how to use the Qt quint16 data type or even how to import it.
Here is my full code:
#!/bin/python3
from PySide2.QtMultimedia import QMediaPlayer, QMediaContent, QAudioProbe, QAudioBuffer
from PySide2.QtCore import QUrl, QCoreApplication, QObject, Signal, Slot
import sys
def main():
app = QCoreApplication()
player = QMediaPlayer()
url = QUrl.fromLocalFile("/home/ubuntu/sound.wav")
content = QMediaContent(url)
player.setMedia(content)
player.setVolume(50)
probe = QAudioProbe()
probe.setSource(player)
probe.audioBufferProbed.connect(processProbe)
player.play()
def processProbe(probe):
print(probe.data())
if __name__ == "__main__":
main()
Output:
shiboken2.shiboken2.VoidPtr(Address 0x2761000, Size 0, isWritable False)
shiboken2.shiboken2.VoidPtr(Address 0x2761000, Size 0, isWritable False)
shiboken2.shiboken2.VoidPtr(Address 0x2761000, Size 0, isWritable False)
shiboken2.shiboken2.VoidPtr(Address 0x2761000, Size 0, isWritable False)
...
I ran into the same issue with a fresh PySide2 5.13.2 environment, and running print(probe.data().toBytes()) returned chunks of size 0 which I knew couldn't be the case because other built-in functionality was accessing the data.
I hate this hack as much as anyone else, but if you want to test things it is possible to access the buffer contents this way (please do not use this in production code):
Find out about the datatype, endian-ness etc of your buffer via format, and infer the proper C type that you'll need (e.g. signed int 16).
Extract the printed address from the VoidPtr printout, and convert it to an integer
Create a numpy array by reading at the given address, with the given type, and by the given amount of frames.
Code:
First of all, somewhere in your app, you'll be connecting your QAudioProbe to your source via setSource, and then the audioBufferProbed signal to a method e.g.:
self.audio_probe.audioBufferProbed.connect(self.on_audio_probed)
Then, the following on_audio_probed functionality will fetch the numpy array and print its norm, which should increase in presence of sound:
import numpy as np
import ctypes
def get_buffer_info(buf):
"""
"""
num_bytes = buf.byteCount()
num_frames = buf.frameCount()
#
fmt = buf.format()
sample_type = fmt.sampleType() # float, int, uint
bytes_per_frame = fmt.bytesPerFrame()
sample_rate = fmt.sampleRate()
#
if sample_type == fmt.Float and bytes_per_frame == 4:
dtype = np.float32
ctype = ctypes.c_float
elif sample_type == fmt.SignedInt and bytes_per_frame == 2:
dtype = np.int16
ctype = ctypes.c_int16
elif sample_type == fmt.UnsignedInt and bytes_per_frame == 2:
dtype = np.uint16
ctype = ctypes.c_uint16
#
return dtype, ctype, num_bytes, num_frames, bytes_per_frame, sample_rate
def on_audio_probed(audio_buffer):
"""
"""
cdata = audio_buffer.constData()
(dtype, ctype, num_bytes, num_frames,
bytes_per_frame, sample_rate) = get_buffer_info(audio_buffer)
pointer_addr_str = str(cdata).split("Address ")[1].split(", Size")[0]
pointer_addr = int(pointer_addr_str, 16)
arr = np.array((ctype * num_frames).from_address(pointer_addr))
print(np.linalg.norm(arr)) # should increase in presence of sound
I just tested it with a QAudioRecorder using 16-bit unsigned wavs, and it worked "fine" (audio looked and sounded good, see screenshot below). Again, this is basically a meme code so anything above showing your fancy audio buffered app to your cousins will be extremely risky, do not use in serious code. But in any case let me know if any other workarounds worked for you, or if this also worked in a different context! Hopefully if the devs see that people are actually using this approach they'll fix the issue much sooner :)
Cheers!
Andres
I have this code snippet running on a raspberry pi. It basically take pictures of people coming in and out of my room.
import RPi.GPIO as GP
import os
import socket
def opencallback(channel):
print(GP.input(channel))
if GP.input(channel):
global closeEvent
closeEvent = 1
else:
global openEvent
openEvent = 1
def transmit(message):
s = socket.create_connection((host, port))
s.send(message)
s.close()
def capture(cam, gpiolist, quick):
GP.output(cam1, gpiolist[0])
GP.output(cam2, gpiolist[1])
GP.output(cam3, gpiolist[2])
if quick:
cmd = "raspistill -o capture_%d.jpg -t 2" % cam
else:
cmd = "raspistill -o capture_%d.jpg" % cam
os.system(cmd)
# init
GP.setwarnings(False)
GP.setmode(GP.BOARD)
cam1 = 7
cam2 = 11
cam3 = 12
doorIn = 40
ledOut = 38
GP.setup(cam1, GP.OUT) # camera Mux1
GP.setup(cam2, GP.OUT) # camera Mux2
GP.setup(cam3, GP.OUT) # camera Mux3
GP.setup(ledOut, GP.OUT) # LED OUT GPIO 20
GP.setup(doorIn, GP.IN) # Door detector in GPIO 21
GP.add_event_detect(doorIn, GP.BOTH, callback=opencallback)
GP.output(ledOut, False)
openEvent = 0
closeEvent = 0
host = '192.168.1.111'
port = 13579
# main
while True:
if openEvent == 1:
transmit("2-01")
capture(2, (False, True, False), True) # front cam
transmit("2-03")
openEvent = 0
else:
pass
if closeEvent == 1:
transmit("2-02")
GP.output(ledOut, True)
capture(3, (False, False, True), False)
GP.output(ledOut, False)
transmit("2-04")
closeEvent = 0
else:
pass
Usually, I run it simply by using a standard call through command line and it doesn't load out the system.
However, I recently converted it to a service using systemd/ systemctl because I wanted to load that script in the background when I boot the pi. Now, this script is gulping a whole processor core by itself (as reported by htop). I didn't change anything to the code itself during the transition and when I run it the old way, it is still working okay. Most of the time, it is simply running the while loop doing nothing and waiting for a callback from GPIO and then execute some functions and go back to the while pass behavior.
My question is: what is causing the difference in computing power consumption between the two execution methods? Is there a way to fix this?
The edge-detection/callback code is efficient, only being invoked when an event takes place. However, the top-level while True: loop is extremely inefficient.
Consider the following modifications:
try:
import Queue as queue # Python 2.x
except ImportError:
import queue # Python 3.x
eventQueue = queue.Queue()
def opencallback(channel):
eventQueue.put(GP.input(channel))
# ...your existing setup code here, specifically including:
GP.add_event_detect(doorIn, GP.BOTH, callback=opencallback)
while True:
event = eventQueue.get()
if event:
transmit("2-01")
capture(2, (False, True, False), True) # front cam
transmit("2-03")
else:
transmit("2-02")
GP.output(ledOut, True)
capture(3, (False, False, True), False)
GP.output(ledOut, False)
transmit("2-04")
Note that this is not related to systemd -- you would have the same CPU usage issues with any other invocation method as well.
Why not have the callbacks trigger the actions for open and close events directly? Just have the loop run a short time.sleep
def opencallback(channel):
print(GP.input(channel))
if GP.input(channel):
transmit("2-02")
GP.output(ledOut, True)
capture(3, (False, False, True), False)
GP.output(ledOut, False)
transmit("2-04")
closeEvent = 0
else:
transmit("2-01")
capture(2, (False, True, False), True) # front cam
transmit("2-03")
According to Python documentation, only recv() blocks but not send(). I wrote the following code trying to make a GUI sudoku game. I made it in such a way that I can update the game board even if the tkinter is executing its mainloop. However, during a test run, I found that if I close the window while the game is updating, the pipe.send() starts to block (I found that out using CPython profiler.) Can anyone please tell me why and, if possible, how to fix this issue?
To produce the issue: close the poped up window while the script is updating. That is, close the window while some numbers are printed to console.
My system: macOS Sierra 10.12.5
import multiprocessing as mp
import threading
import random
import time
try:
import tkinter as tk # Python3
except ImportError:
import Tkinter as tk # Python2
class VisualizedBoard:
def __init__(self,input_string,pipe):
'''input_string: a string has a length of at least 81 that represent the board from top-left to bottom right.
empty cell is 0'''
self.update_scheduled=False
self.pipe=pipe
# create board
self.root = tk.Tk()
self.canvas = tk.Canvas(self.root, width=500, height=500)
self.canvas.create_rectangle(50, 50, 500, 500, width=2)
for i in range(1, 10):
self.canvas.create_text(25 + 50 * i, 30, text=str(i))
self.canvas.create_text(30, 25 + 50 * i, text=str(i))
self.canvas.create_line(50 + 50 * i, 50, 50 + 50 * i, 500, width=2 if i % 3 == 0 else 1)
self.canvas.create_line(50, 50 + 50 * i, 500, 50 + 50 * i, width=2 if i % 3 == 0 else 1)
for i in range(81):
if input_string[i] != '0':
self.canvas.create_text(75 + 50 * (i // 9), 75 + 50 * (i % 9), tags=str((i//9+1,i%9+1)).replace(' ',''),text=input_string[i], fill='black')
self.canvas.pack()
self.root.attributes('-topmost', True)
self.root.geometry('550x550+%d+%d' % ((self.root.winfo_screenwidth() - 550) // 2, (self.root.winfo_screenheight() - 550) // 2))
self.root.wm_protocol('WM_DELETE_WINDOW',lambda :(self.root.destroy()))
threading.Thread(target=self.listen, args=()).start()
self.root.mainloop()
def update(self,coordinate,value,color='magenta'):
"""
:parameter coordinate: a tuple (x,y)
:parameter value: single digit
:returns: None
"""
try:
assert isinstance(coordinate,tuple)
except AssertionError:
print('Update Failed. Coordinate should be a tuple (x,y)')
coordinate_tag=str(coordinate).replace(' ','')
self.canvas.delete(coordinate_tag)
if value != 0 and value != '0':
self.canvas.create_text(25+50*coordinate[0],25+50*coordinate[1],tags=coordinate_tag,text=str(value),fill=color)
self.postponed_update()
#self.canvas.update()
def postponed_update(self):
if not self.update_scheduled:
self.canvas.after(50,self.scheduled_update)
self.update_scheduled=True
def scheduled_update(self):
self.canvas.update()
self.update_scheduled=False
def new_board(self,input_string):
self.root.destroy()
return VisualizedBoard(input_string,self.pipe)
def listen(self):
try:
while True:
msg=self.pipe.recv()
self.update(*msg)
except EOFError:
self.pipe.close()
tk.Label(self.root,text='Connection to the main script has been closed.\nIt is safe to close this window now.').pack()
except Exception as m:
self.pipe.close()
print('Error during listing:',m)
class BoardConnection:
def __init__(self,input_string):
ctx = mp.get_context('spawn')
self.receive,self.pipe=ctx.Pipe(False)
self.process=ctx.Process(target=VisualizedBoard,args=(input_string,self.receive))
self.process.start()
def update(self,coordinate,value,color='magenta'):
"""
:parameter coordinate: a tuple (x,y)
:parameter value: single digit
:returns: None
"""
self.pipe.send((coordinate,value,color))
def close(self):
self.pipe.close()
self.process.terminate()
if __name__ == "__main__":
b=BoardConnection('000000000302540000050301070000000004409006005023054790000000050700810000080060009')
start = time.time()
for i in range(5000): #test updating using random numbers
b.update((random.randint(1, 9), random.randint(1, 9)), random.randrange(10))
print(i)
print(time.time() - start)
Python Pipe is an abstractions on top of OS nameless pipes.
An OS pipe is generally implemented as a memory buffer of a certain size within the kernel. By default, if the buffer fills up the next call to send/write will block.
If you want to be able to continue publishing data even if no consumer is consuming it, you should either use a multiprocessing.Queue or asyncio facilities.
The multiprocessing.Queue employs a "limitless" buffer and a thread to push the data into the OS pipe. If the pipe gets full the caller will continue running as the published data will be piled up in the Queue buffer.
IIRC, asyncio sets the pipe O_NONBLOCK flag and waits for the pipe to be consumed. Additional messages are stored within a "limitless" buffer as for the multiprocessing.Queue.
While running program through the terminal we can stop the program by pressing 'Ctrl+c' and it will show the message as 'KeyboardInterrupt' . So, is there any way to do the sane thing by clicking the push-button in PyQt.
If your program is running a loop, you can call processEvents periodically to allow the gui time to update (which should allow you to click a button to close the application):
count = 0
while True:
count += 1
if not count % 50:
QtGui.qApp.processEvents()
# do stuff...
In my script to interrupt an infinite loop I also used QtGui.qApp.processEvents() and it worked out fine. The infinite loop writes to and reads data from a serial port and the user can interrupt the loop with a push button (1.condition).
def Move_Right(self):
# move the slide right
cmdPack = struct.pack(cmdStruct, Address, Rotate_Right, 0, Motor5, Speed5)
dataByte = bytearray(cmdPack)
checksumInt = sum(dataByte[:]) % 256
msgPack = struct.pack(msgStruct, Address, Rotate_Right, 0, Motor5, Speed5, checksumInt)
ser0.flushOutput() # Clear output buffer
ser0.write(msgPack)
# read the switch status
cmdPack = struct.pack(cmdStruct, Address, Command.GAP, 10, Motor5, 0)
dataByte = bytearray(cmdPack)
checksumInt = sum(dataByte[:]) % 256
msgPack = struct.pack(msgStruct, Address, Command.GAP, 10, Motor5, 0, checksumInt)
ser0.flushOutput() # Clear output buffer
# check the switch status with an infinite write/read loop with two break out conditions
while True:
QtGui.qApp.processEvents() # 1. condition: interrupt with push button
ser0.write(msgPack)
reply = ser0.read(9)
answer = struct.unpack('>BBBBlB', reply)
value = answer[4]
command = answer[3]
if (command == 6) and (value == 1): # 2. condition: interrupt with limit switch
print 'end of line'
Stop_Motor5()
break