I am currently working working on a program that tries to replicate labview functionality by establishing a connection to SR830 via RS-232. For some reasons, I am not able to record data and I cannot seem to figure out why. I tried to google about it but I did not find anything. If someone could please help me figure out what I am doing wrong.
Here is my code:
import serial
import tkinter as tk
from datetime import datetime
import time
import os
import atexit
import threading
import numpy as np
import math
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
class SR830:
'''SR830 Class creates an object that can interface with and control an SR830 via RS-232.
Class data structure allows the SR830 code to be imported into other scripts via "from SSTR_Serial import SR830"
This can be used to encapsulate SR830 interface mechanics into a larger control scheme...
by passing the class a frame to pack itself into as the frame argument.'''
def command(self, command_string, command_value): #Basic command protocol, can be used as a template for other command functions.
#Note: queries should have null command_value arguments (command_value = '').
packet = command_string+str(command_value)+'\r' #Can be adjusted to include checksums/parity if needed.
try:
self.inst.write(packet.encode('ascii'))
except AttributeError as AE:
print(AE)
def create_log(self):
try: #Makes log file if it does not exist already.
log_dir = self.name+'_logs'
os.mkdir(log_dir)
except OSError:#Log file already exists
pass
self.log_open = datetime.now() #Var to hold time log was created (Different from when timer is started.)
date = str(self.log_open).split(' ')[0] #Date that log file was opened.
h = 0
a = False
while not a:
h += 1
log_str = log_dir+'/'+self.name+"_"+date+'_'+str(h)+'.txt'
log_path = os.path.join(os.getcwd(), log_str)
if os.path.exists(log_path):
pass
else:
a = True
self.log = open(log_str, "w+")
self.log.write(self.name+" log created on "+ str(self.log_open)+"\nTime \tShift (Degrees)\tAmplitude (mV)\tR ()\tTheta ()\n") #Writing opening lines.
def start_graph(self): #Should be threaded at begining. This begins making the graph and recording data when the start/record buttons are pressed.
def get_vals(self):
current_time = time.time()#Get current time
if (current_time - self.previous_time) >= self.sample_time: #Time is equal or passed sampling time.
time_elapsed = current_time - self.start_time #Total time elapsed since transmission begun.
#Transmission zone:
try: #Querying and storing commands below
self.command("PHAS?",'')
phase_shift = self.inst.read(5).decode('ascii').replace('\r','')
self.command('SLVL?','')
phase_amplitude = self.inst.read(5).decode('ascii').replace('\r','')
self.shifts = np.append(self.shifts,[phase_shift]); self.amplitudes = np.append(self.amplitudes,[phase_amplitude]); self.times = np.append(self.times,[time_elapsed]) #Will only append time if both other arrays are appended.
#^ Append on one line to keep all lengths consistant when switching threads.
#Variables that are queried but not logged.
self.command('OEXP?1','')
R = self.inst.read(5).decode('ascii').replace('\r','')
self.command('OEXP?2','')
Theta = self.inst.read(5).decode('ascii').replace('\r','')
except AttributeError as AE:#Exceptions for testing without connection to device
print(AE)
'''
#This area generates random sine waves. Only use for testing without connection to actual device.---------
self.times = np.append(self.times,[len(self.times)])
self.shifts = np.append(self.shifts,[math.sin(len(self.times))])
self.amplitudes = np.append(self.amplitudes, [5*math.cos(len(self.times))])
phase_shift = 5
phase_amplitude = 7
R = 10
Theta = 11
print(str(len(self.times)))
print(str(len(self.shifts)))
print(str(len(self.amplitudes)))
#End test zone ---------------------------------------------------------------------------------------
'''
#Remove extra elements to reduce memeory usage for long runs
while len(self.times) > 200: #Integer on RHS of inequality is max number of elements.
self.shifts = self.shifts[2:-1:1]; self.amplitudes = self.amplitudes[2:-1:1]; self.times=self.times[2:-1:1]
#Log writing zone:
if self.recording is True: #Updates log, otherwise only updates graph.
self.log.write(str(round(time_elapsed-self.pause_time,4))+'\t'+str(phase_shift)+'\t\t'+str(phase_amplitude)+'\t\t'+str(R)+'\t'+str(Theta)+'\n')
self.previous_time = current_time #Resets time value for calculating interval.
while self.running: #Loops as long as running is true and kills thread once runing is false.
get_vals(self)
def animate(self, i): #Only redraw matplotlib aspects that get cleared with a.clear() and a2.clear() to conserve memory.
#Todo - optimize drawing order
try:
self.a.clear()
self.a.plot(self.times,self.shifts, label='Phase shift ()', color='tab:blue')
self.a.set_ylabel('Shift')
self.a.set_ylabel('Shift', color='tab:blue')
self.a.set_xlabel('Time (s)')
self.a.legend(loc='upper left')
self.a2.clear()
self.a2.plot(self.times,self.amplitudes,label='Phase amplitude ()',color='tab:red')
self.a2.set_ylabel('Amplitude', color='tab:red')
self.a2.legend(loc='upper right')
self.a.get_yaxis_transform()#Fits axis to widget area.
except ValueError as VE:
print(VE)
def clear_graph(self): #Clears all arrays to make graph reset. Does not erase logged data.
self.times = np.array([])
self.shifts = np.array([])
self.amplitudes = np.array([])
def startstop(self): #Flips button to begin or stop graphing.
if self.running is False: #Recording and graphing is off. Calling function will turn it on and activate the following lines
self.running = True
self.clear_graph() #Clears last run.
self.run_btn.config(text='Stop', bg='red')
#self.start_graph()
self.sample_time = float(self.sample_box.get())
self.pause_time = 0 #Resets pause adjustment time.
self.start_time = time.time() #Resets start time. TODO-Add adjustments for pauses
self.pause_point = time.time() #Last time that pause activated. Used to calculate pause_time later.
self.graph_thread = threading.Thread(target=self.start_graph)
self.graph_thread.start()
else: #graphing is on. Calling function will turn it off and activate the following line
self.running = False
self.run_btn.config(text='Start',bg='green')
self.recording=False
self.record_btn.config(text='Start recording', bg="green")
try: #Shuts down log file if it exists.
self.log.close()
except AttributeError as AE:
print(AE)
def startstop_record(self): #Flips recording button.
if self.running is False:
self.startstop() #Makes sure data is being transmitted before recording starts.
if self.recording is False: #Device is running, but not recording. Command turns recording on.
self.record_btn.config(text='Pause recording', bg="yellow")
try:
self.log.write('') #Attempts to write an empty string to test if file is open.
except AttributeError: #File has not been created -> run function to open a new log.
self.create_log()
except ValueError: #Previous log file has been closed -> run function to open new log.
self.create_log()
self.recording = True
self.pause_time += time.time() - self.pause_point #Adjusts for
else: #Device is running AND recording. Command pauses recording.
self.recording = False
self.pause_point = time.time()
self.record_btn.config(text='Start recording', bg="green")
def __init__(self, name, frame):
self.name = name
self.running = False
self.recording = False
#Read config -----------------------------------------------------------------------
config = open('SR830 config.txt','w+')
try:
config_read = config.readlines()
config_read = config_read[1].split('\t')
except IndexError: #config coudl not be read - creates default file.
config.write('COM Baudrate\ttimeout\tsampling time\n10\t9600\t0.1\t1')
config.close()
config = open('SR830 config.txt','r')
config_read = config.readlines()
config_read = config_read[1].split('\t')
com = config_read[0]
baud = int(config_read[1])
self.config_timeout = float(config_read[2])
self.sample_time = float(config_read[3])
config.close()
#establish communication -----------------------------------------------------------
com = 'COM'+str(com) #TODO- make this read from config
try:
SR_inst = serial.Serial(port=com, baudrate=baud, timeout=self.config_timeout) #Opens communication.
#Note: SR830 has adjustablebaud rate and parity, but defaults to 9600 and none, respectively.
self.inst = SR_inst
self.command('OUTX',0) #Tells device to output responses via RS-232.
print(self.inst.read(5)) #Prints response to OUTX command.
#self.command('OUTX?','') #TEST - see if communication has switched.
#print(self.inst.read(10)) #Should print b'0\r' for RS-232 communication.
self.command('DDEF',110)#Sets CH1 to R.
print(self.inst.read(5))
self.command('DDEF',210)#Sets CH2 to Theta.
print(self.inst.read(5))
except ValueError as ve:
SR_inst = 0
print(ve)
except AttributeError as ae:
SR_inst = 0
print (ae)
except NameError as ne:
print(ne)
SR_inst = 0
except serial.SerialException as SE:
SR_inst = 0
print(SE)
#Create Tkinter GUI------------------------------------------------------------------
id_label=tk.Label(frame, text=name)
id_label.grid(row=0,column=0, sticky=tk.N+tk.S+tk.E+tk.W)
version_label = tk.Label(frame, text = 'Version 0.1')
version_label.grid(row=0, column=1, sticky=tk.N+tk.S+tk.E+tk.W)
sample_label = tk.Label(frame, text="Sampling time =")
sample_label.grid(row=1,column=0,sticky=tk.N+tk.S+tk.E+tk.W)
self.sample_box = tk.Entry(frame)
self.sample_box.insert(tk.END, self.sample_time)
self.sample_box.grid(row=1,column=1,sticky=tk.N+tk.S+tk.E+tk.W)
self.run_btn = tk.Button(frame, text='Start', command = self.startstop)
self.run_btn.grid(row=2,column=0,sticky=tk.N+tk.S+tk.E+tk.W)
self.run_btn.config(bg="green")
self.record_btn = tk.Button(frame, text='Start recording', command = self.startstop_record)
self.record_btn.grid(row=2,column=1,sticky=tk.N+tk.S+tk.E+tk.W)
self.record_btn.config(bg="green")
#Graph setup:
self.f = Figure(figsize=(5,5),dpi=100) #Figure that graphs appears in
self.a = self.f.add_subplot() #111 means there is only 1 chart. use a.plot to plot lists.
self.a2 = self.a.twinx()
self.graph_canvas = FigureCanvasTkAgg(self.f, frame)
self.graph_canvas.get_tk_widget().grid(row=3, column=0,columnspan=2)
#Make grid resizeable
for row in range(4): #Number of rows
try:
tk.Grid.rowconfigure(frame,row,weight=2)
except AttributeError as AE: #FOr unfilled rows & columns
print(AE)
for column in range(2): #Number of columns
try:
#root.grid_columnconfigure(column, weight=1)
tk.Grid.columnconfigure(frame,column,weight=1)
except AttributeError as AE: #For unfilled rows & columns
print(AE)
#Extra declarations
self.sample_time = float(self.sample_box.get())-2*self.config_timeout #Adjusts for timeout of device.
self.times = np.array([]) #Holds time values queried (May not be recorded)
self.shifts = np.array([])#Holds shift values queried.
self.amplitudes = np.array([])#Holds amplitude values queried.
#self.start_time = time.time() #TODO - Move this somewhere better
self.previous_time = 0
time.sleep(1)
self.ani = animation.FuncAnimation(self.f,self.animate, interval=1000)
def SR830_exit(self): #Exit handler.
self.running = False
self.recording = False
try:
self.inst.close()
except AttributeError as AE:
print(AE)
root.destroy()
exit()
#TEST AREA: ------------------------------------------------------------------------------
# Create tk root -------------------
root = tk.Tk()
root.title('SR830')
frame1 = tk.Frame(root)
frame1.pack()
#Create instrument object ---------
sr1 = SR830("SSTR", frame1) #TODO - remove once testing is done.
atexit.register(sr1.SR830_exit)
root.protocol('WM_DELETE_WINDOW', sr1.SR830_exit)
root.mainloop()
Related
Usually with audio you would access volume with:
sound.volume = 10
However with Pydub, the volume is accessed using:
sound + 10
The problem with this as I cannot exactly 'set' the volume, just adjust the volume that the song is currently at. I would like to create a Tkinter slider that I can vary between not hearable and loud. This is what I have so far:
root = tk.Tk()
root.geometry("500x500")
song1 = AudioSegment.from_file("./song.mp3")
def current_value(event):
song1 + slider.get()
#song1.volume = event
print(event)
slider = ttk.Scale(root, from_=-50, to=50, orient="horizontal",command=current_value)
slider.place(rely=.5, relx=.5)
def play_music(song):
play(song)
thread1 = threading.Thread(target=play_music, args=(song1,))
thread1.start()
root.mainloop()
#This mostly just lags the audio file
fixed with:
from pycaw.pycaw import AudioUtilities
class AudioController:
def __init__(self, process_name):
self.process_name = process_name
self.volume = self.process_volume()
def process_volume(self):
sessions = AudioUtilities.GetAllSessions()
for session in sessions:
interface = session.SimpleAudioVolume
if session.Process and session.Process.name() == self.process_name:
#print("Volume:", interface.GetMasterVolume()) # debug
return interface.GetMasterVolume()
def set_volume(self, decibels):
sessions = AudioUtilities.GetAllSessions()
for session in sessions:
interface = session.SimpleAudioVolume
if session.Process and session.Process.name() == self.process_name:
# only set volume in the range 0.0 to 1.0
self.volume = min(1.0, max(0.0, decibels))
interface.SetMasterVolume(self.volume, None)
#print("Volume set to", self.volume) # debug
def volume_slider_controller(event):
audio_controller = AudioController("python.exe") #will need to be punge.exe
audio_controller.set_volume(float(event))
Hi I am trying to run a code to validate my acceleromter sensor on RPI4. Seems my libray does not have a sample rate optio, 'fs' as I keep getting the error;
TypeError: butter() got an unexpected keyword argument 'fs'
Any ideas how i could fix this?
def imu_integrator():
#############################
# Main Loop to Integrate IMU
#############################
#
data_indx = 1 # index of variable to integrate
dt_stop = 5 # seconds to record and integrate
plt.style.use('ggplot')
plt.ion()
fig,axs = plt.subplots(3,1,figsize=(12,9))
break_bool = False
while True:
#
##################################
# Reading and Printing IMU values
##################################
#
accel_array,t_array = [],[]
print("Starting Data Acquisition")
[axs[ii].clear() for ii in range(0,3)]
t0 = time.time()
loop_bool = False
while True:
try:
ax,ay,az,wx,wy,wz = mpu6050_conv() # read and convert mpu6050 data
mx,my,mz = AK8963_conv() # read and convert AK8963 magnetometer data
t_array.append(time.time()-t0)
data_array = [ax,ay,az,wx,wy,wz,mx,my,mz]
accel_array.append(accel_fit(data_array[data_indx],
*accel_coeffs[data_indx]))
if not loop_bool:
loop_bool = True
print("Start Moving IMU...")
except:
continue
if time.time()-t0>dt_stop:
print("Data Acquisition Stopped")
break
if break_bool:
break
#
##################################
# Signal Filtering
##################################
#
Fs_approx = len(accel_array)/dt_stop
b_filt,a_filt = signal.butter(4,5,'low',fs=Fs_approx)
accel_array = signal.filtfilt(b_filt,a_filt,accel_array)
accel_array = np.multiply(accel_array,9.80665)
I have been searching stackoverflow for a solution to the issue of an interactive mpl figure when streaming data to it. I've gotten close with the below example, but when I plot this no matter what mods I make to the code I'm unable to interact with the figure window. What concept am I missing that would allow me to stream data to this window AND drag the window around? As you can see it opens up and plots, but if I grab the frame to move it to a different location it crashes and most of the time is stuck in place.
I'm using Python 2.7, Paycharm and Windows 10.
Here is a helpful example I'm working with.
Example-from-documentation
Is the problem because I'm using plt.show()? My test code consists of 3 files, a datagen, data consumer (plotter) and a top level file (test bench) instantiating and starting the data generator and plotting module. I am just appending 62bytes of sine wave data to the end of an array and plotting it so it looks like it is scrolling by.
Test bench:
NB_DataGen -> NB_Plotter (receives 62 bytes of data and plots).
MODULE1: DATA PLOTTING MODULE
# This is the no blitting data plot module built out as a threaded module.
#
#
# Notes:
# 1. Bug in frame rate code
# 2. Going to try to remove queue from plotter and just have a direct access call for
# direct writes to plot. Queue seems to be bogging down window and I can't drag it
# around.
#
try:
import Queue as queue
except:
import queue
import numpy as np
from matplotlib import pyplot as plt
import time
import threading
import matplotlib
print(matplotlib.__version__)
class BlitManager:
def __init__(self, canvas, animated_artists=()):
"""
Parameters
----------
canvas : FigureCanvasAgg
The canvas to work with, this only works for sub-classes of the Agg
canvas which have the `~FigureCanvasAgg.copy_from_bbox` and
`~FigureCanvasAgg.restore_region` methods.
animated_artists : Iterable[Artist]
List of the artists to manage
"""
self.canvas = canvas
self._bg = None
self._artists = []
for a in animated_artists:
self.add_artist(a)
# grab the background on every draw
self.cid = canvas.mpl_connect("draw_event", self.on_draw)
def on_draw(self, event):
"""Callback to register with 'draw_event'."""
cv = self.canvas
if event is not None:
if event.canvas != cv:
raise RuntimeError
self._bg = cv.copy_from_bbox(cv.figure.bbox)
self._draw_animated()
def add_artist(self, art):
"""
Add an artist to be managed.
Parameters
----------
art : Artist
The artist to be added. Will be set to 'animated' (just
to be safe). *art* must be in the figure associated with
the canvas this class is managing.
"""
if art.figure != self.canvas.figure:
raise RuntimeError
art.set_animated(True)
self._artists.append(art)
def _draw_animated(self):
"""Draw all of the animated artists."""
fig = self.canvas.figure
for a in self._artists:
fig.draw_artist(a)
def update(self):
"""Update the screen with animated artists."""
cv = self.canvas
fig = cv.figure
# paranoia in case we missed the draw event,
if self._bg is None:
self.on_draw(None)
else:
# restore the background
cv.restore_region(self._bg)
# draw all of the animated artists
self._draw_animated()
# update the GUI state
cv.blit(fig.bbox)
# let the GUI event loop process anything it has to do
cv.flush_events()
#
# Main Class
#
class NB_Plotter4(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.i = 0
# thread loop flag
self.thread_event = threading.Event()
self.thread_event.set() # set thread by default
# create plot objects
self.fig = plt.figure()
self.ax1 = self.fig.add_subplot(1,1,1)
self.ax1.grid()
self.line, = self.ax1.plot([], lw=3)
self.text = self.ax1.text(0.8, 0.5, "")
self.x_new = np.linspace(0.0, 200.0, num=1000)
self.y_total = np.empty(1000, dtype=float)
#set limits
self.ax1.set_xlim(self.x_new.min(), self.x_new.max())
self.ax1.set_ylim([-1.1, 1.1])
# start timer for frame counter
self.t_start = time.time()
#self.bm = BlitManager(self.fig.canvas, [self.line, self.text])
self.bm = BlitManager(self.fig.canvas, [self.line])
plt.show(block=False)
plt.pause(0.1)
#
# main thread loop
#
def Write(self, data):
# need to grab y-data here from queue
self.y_ndata = data
self.y_total = np.concatenate([self.y_total[62:], self.y_ndata])
self.i = self.i + 1
#
# Over-ride thread run method
#
def run(self):
while self.thread_event.is_set():
self.line.set_data(self.x_new, self.y_total)
tx = 'Mean Frame Rate:\n {fps:.5f}FPS'.format(fps=((self.i + 1) / (time.time() - self.t_start)))
self.text.set_text(tx)
self.bm.update()
MODULE2: DATA GENERATION MODULE
# This is the no blitting data gen module. This module is intended to produce data
# in 62 byte blocks resulting in sine wave data blocks. This is initally meant to
# spoof my DR500 USB payload size so I can' drive some real time plotting.
#
#
# Notes:
#
#
#
try:
import Queue as queue
except:
import queue
import numpy as np
import threading
import time
#
# Main Class
#
# For the 62 byte class the rounding in the x vector produces some small errors. This shouldn't
# be a big problem since the resolution is so high.
#
class NB_DataGen2(threading.Thread):
def __init__(self, Plotter):
threading.Thread.__init__(self)
self.y_data = np.empty(62, dtype=float)
self.x_data = np.linspace(0.0, np.pi/62.0, num=62)
self.offset_val = self.x_data[1]
self.Plotter_Handle = Plotter
self.inc_cnt = 0.0
self.first_it_flag = 0 # first iteration flag
# thread loop flag
self.thread_event = threading.Event()
self.thread_event.set() # set thread by default
#
# Produce 62 byte packet of sine wave data
# - produce next 62 byte chunk of sine wave data
def Get62ByteSine(self, debug=False):
# hit for iterations > 0
if(self.first_it_flag > 0):
# gen
self.x_data = self.x_data + (np.pi / 62.0) + self.offset_val
self.y_data = np.sin(self.x_data)
if(debug == True):
print(self.y_data)
return self.y_data
# hit for iterations < 1 -> (first iteration)
else:
# first iteration
self.x_data = self.x_data
self.y_data = np.sin(self.x_data)
if (debug == True):
print(self.y_data)
self.inc_cnt = self.inc_cnt + 1.0
# flip first run flag
self.first_it_flag = 1
return self.y_data
#
# Ignore / Not in use
#
# Used to check the error from the last value in one block (62 byte block)
# and the first value of the next block. the difference in the two should
# match the offset value roughly. Enable print funcitons in the above
# Get62ByteSine function for this to work.
#
def CheckError(self):
self.Get62ByteSine(True)
self.Get62ByteSine(True)
self.Get62ByteSine(True)
# print offset
print(self.offset_val)
#
# Kill thread
#
def KillThread(self):
self.thread_event.clear()
#
# main thread loop
#
def run(self):
while self.thread_event.is_set():
self.Plotter_Handle.Write(self.Get62ByteSine())
time.sleep(1)
MODULE3: TOP LEVEL TEST BENCH
# This is the no blitting test bench top module
#
#
# Notes:
#
#
#
from NB_DataGen2 import NB_DataGen2
from NB_Plotter4 import NB_Plotter4
#
# Testbench Class
#
class NB_TestBench(object):
def __init__(self):
# create data/plot objects (DUTs) - obj's under test
self.Plotter = NB_Plotter4()
self.DataGen = NB_DataGen2(self.Plotter)
def Start(self):
self.DataGen.start()
self.DataGen.isDaemon()
self.Plotter.start()
self.Plotter.isDaemon()
# Run test bench
NB = NB_TestBench()
NB.Start()
Long story short - I'm trying to run this code to plot incoming data and be able to drag the window around or just generally interact with it via the mouse. Does anyone see where I went wrong?
I'm working on a Raspberry Pi (3 B+) making a data collection device and I'm
trying to spawn a process to record the data coming in and write it to a file. I have a function for the writing that works fine when I call it directly.
When I call it using the multiprocess approach however, nothing seems to happen. I can see in task monitors in Linux that the process does in fact get spawned but no file gets written, and when I try to pass a flag to it to shut down it doesn't work, meaning I end up terminating the process and nothing seems to have happened.
I've been over this every which way and can't see what I'm doing wrong; does anyone else? In case it's relevant, these are functions inside a parent class, and one of the functions is meant to spawn another as a thread.
Code I'm using:
from datetime import datetime, timedelta
import csv
from drivers.IMU_SEN0 import IMU_SEN0
import multiprocessing, os
class IMU_data_logger:
_output_filename = ''
_csv_headers = []
_accelerometer_headers = ['Accelerometer X','Accelerometer Y','Accelerometer Z']
_gyroscope_headers = ['Gyroscope X','Gyroscope Y','Gyroscope Z']
_magnetometer_headers = ['Bearing']
_log_accelerometer = False
_log_gyroscope= False
_log_magnetometer = False
IMU = None
_writer=[]
_run_underway = False
_process=[]
_stop_value = 0
def __init__(self,output_filename='/home/pi/blah.csv',log_accelerometer = True,log_gyroscope= True,log_magnetometer = True):
"""data logging device
NOTE! Multiple instances of this class should not use the same IMU devices simultaneously!"""
self._output_filename = output_filename
self._log_accelerometer = log_accelerometer
self._log_gyroscope = log_gyroscope
self._log_magnetometer = log_magnetometer
def __del__(self):
# TODO Update this
if self._run_underway: # If there's still a run underway, end it first
self.end_recording()
def _set_up(self):
self.IMU = IMU_SEN0(self._log_accelerometer,self._log_gyroscope,self._log_magnetometer)
self._set_up_headers()
def _set_up_headers(self):
"""Set up the headers of the CSV file based on the header substrings at top and the input flags on what will be measured"""
self._csv_headers = []
if self._log_accelerometer is not None:
self._csv_headers+= self._accelerometer_headers
if self._log_gyroscope is not None:
self._csv_headers+= self._gyroscope_headers
if self._log_magnetometer is not None:
self._csv_headers+= self._magnetometer_headers
def _record_data(self,frequency,stop_value):
self._set_up() #Run setup in thread
"""Record data function, which takes a recording frequency, in herz, as an input"""
previous_read_time=datetime.now()-timedelta(1,0,0)
self._run_underway = True # Note that a run is now going
Period = 1/frequency # Period, in seconds, of a recording based on the input frequency
print("Writing output data to",self._output_filename)
with open(self._output_filename,'w',newline='') as outcsv:
self._writer = csv.writer(outcsv)
self._writer.writerow(self._csv_headers) # Write headers to file
while stop_value.value==0: # While a run continues
if datetime.now()-previous_read_time>=timedelta(0,1,0): # If we've waited a period, collect the data; otherwise keep looping
print("run underway value",self._run_underway)
if datetime.now()-previous_read_time>=timedelta(0,Period,0): # If we've waited a period, collect the data; otherwise keep looping
previous_read_time = datetime.now() # Update previous readtime
next_row = []
if self._log_accelerometer:
# Get values in m/s^2
axes = self.IMU.read_accelerometer_values()
next_row += [axes['x'],axes['y'],axes['z']]
if self._log_gyroscope:
# Read gyro values
gyro = self.IMU.read_gyroscope_values()
next_row += [gyro['x'],gyro['y'],gyro['z']]
if self._log_magnetometer:
# Read magnetometer value
b= self.IMU.read_magnetometer_bearing()
next_row += b
self._writer.writerow(next_row)
# Close the csv when done
outcsv.close()
def start_recording(self,frequency_in_hz):
# Create recording process
self._stop_value = multiprocessing.Value('i',0)
self._process = multiprocessing.Process(target=self._record_data,args=(frequency_in_hz,self._stop_value))
# Start recording process
self._process.start()
print(datetime.now().strftime("%H:%M:%S.%f"),"Data logging process spawned")
print("Logging Accelerometer:",self._log_accelerometer)
print("Logging Gyroscope:",self._log_gyroscope)
print("Logging Magnetometer:",self._log_magnetometer)
print("ID of data logging process: {}".format(self._process.pid))
def end_recording(self,terminate_wait = 2):
"""Function to end the recording multithread that's been spawned.
Args: terminate_wait: This is the time, in seconds, to wait after attempting to shut down the process before terminating it."""
# Get process id
id = self._process.pid
# Set stop event for process
self._stop_value.value = 1
self._process.join(terminate_wait) # Wait two seconds for the process to terminate
if self._process.is_alive(): # If it's still alive after waiting
self._process.terminate()
print(datetime.now().strftime("%H:%M:%S.%f"),"Process",id,"needed to be terminated.")
else:
print(datetime.now().strftime("%H:%M:%S.%f"),"Process",id,"successfully ended itself.")
====================================================================
ANSWER: For anyone following up here, it turns out the problem was my use of the VS Code debugger which apparently doesn't work with multiprocessing and was somehow preventing the success of the spawned process. Many thanks to Tomasz Swider below for helping me work through issues and, eventually, find my idiocy. The help was very deeply appreciated!!
I can see few thing wrong in your code:
First thing
stop_value == 0 will not work as the multiprocess.Value('i', 0) != 0, change that line to
while stop_value.value == 0
Second, you never update previous_read_time so it will write the readings as fast as it can, you will run out of disk quick
Third, try use time.sleep() the thing you are doing is called busy looping and it is bad, it is wasting CPU cycles needlessly.
Four, terminating with self._stop_value = 1 probably will not work there must be other way to set that value maybe self._stop_value.value = 1.
Well here is a pice of example code based on the code that you have provided that is working just fine:
import csv
import multiprocessing
import time
from datetime import datetime, timedelta
from random import randint
class IMU(object):
#staticmethod
def read_accelerometer_values():
return dict(x=randint(0, 100), y=randint(0, 100), z=randint(0, 10))
class Foo(object):
def __init__(self, output_filename):
self._output_filename = output_filename
self._csv_headers = ['xxxx','y','z']
self._log_accelerometer = True
self.IMU = IMU()
def _record_data(self, frequency, stop_value):
#self._set_up() # Run setup functions for the data collection device and store it in the self.IMU variable
"""Record data function, which takes a recording frequency, in herz, as an input"""
previous_read_time = datetime.now() - timedelta(1, 0, 0)
self._run_underway = True # Note that a run is now going
Period = 1 / frequency # Period, in seconds, of a recording based on the input frequency
print("Writing output data to", self._output_filename)
with open(self._output_filename, 'w', newline='') as outcsv:
self._writer = csv.writer(outcsv)
self._writer.writerow(self._csv_headers) # Write headers to file
while stop_value.value == 0: # While a run continues
if datetime.now() - previous_read_time >= timedelta(0, 1,
0): # If we've waited a period, collect the data; otherwise keep looping
print("run underway value", self._run_underway)
if datetime.now() - previous_read_time >= timedelta(0, Period,
0): # If we've waited a period, collect the data; otherwise keep looping
next_row = []
if self._log_accelerometer:
# Get values in m/s^2
axes = self.IMU.read_accelerometer_values()
next_row += [axes['x'], axes['y'], axes['z']]
previous_read_time = datetime.now()
self._writer.writerow(next_row)
# Close the csv when done
outcsv.close()
def start_recording(self, frequency_in_hz):
# Create recording process
self._stop_value = multiprocessing.Value('i', 0)
self._process = multiprocessing.Process(target=self._record_data, args=(frequency_in_hz, self._stop_value))
# Start recording process
self._process.start()
print(datetime.now().strftime("%H:%M:%S.%f"), "Data logging process spawned")
print("ID of data logging process: {}".format(self._process.pid))
def end_recording(self, terminate_wait=2):
"""Function to end the recording multithread that's been spawned.
Args: terminate_wait: This is the time, in seconds, to wait after attempting to shut down the process before terminating it."""
# Get process id
id = self._process.pid
# Set stop event for process
self._stop_value.value = 1
self._process.join(terminate_wait) # Wait two seconds for the process to terminate
if self._process.is_alive(): # If it's still alive after waiting
self._process.terminate()
print(datetime.now().strftime("%H:%M:%S.%f"), "Process", id, "needed to be terminated.")
else:
print(datetime.now().strftime("%H:%M:%S.%f"), "Process", id, "successfully ended itself.")
if __name__ == '__main__':
foo = Foo('/tmp/foometer.csv')
foo.start_recording(20)
time.sleep(5)
print('Ending recording')
foo.end_recording()
I've got a program that will eventually receive data from an external source over serial, but I'm trying to develop the display-side first.
I've got this "main" module that has the simulated data send and receive. It updates a global that is used by a Matplotlib stripchart. All of this works.
#-------------------------------------------------------------------------------
# Name: BBQData
# Purpose: Gets the data from the Arduino, and runs the threads.
#-------------------------------------------------------------------------------
import time
import math
import random
from threading import Thread
import my_globals as bbq
import sys
import BBQStripChart as sc
import serial
import BBQControl as control
ser = serial.serial_for_url('loop://', timeout=10)
def simData():
newTime = time.time()
if not hasattr(simData, "lastUpdate"):
simData.lastUpdate = newTime # it doesn't exist yet, so initialize it
simData.firstTime = newTime # it doesn't exist yet, so initialize it
if newTime > simData.lastUpdate:
simData.lastUpdate = newTime
return (140 + 0.05*(simData.lastUpdate - simData.firstTime), \
145 + 0.022*(simData.lastUpdate - simData.firstTime), \
210 + random.randrange(-10, 10))
else:
return None
def serialDataPump():
testCtr = 0;
while not bbq.closing and testCtr<100:
newData = simData()
if newData != None:
reportStr = "D " + "".join(['{:3.0f} ' for x in newData]) + '\n'
reportStr = reportStr.format(*newData)
ser.write(bytes(reportStr, 'ascii'))
testCtr+=1
time.sleep(1)
bbq.closing = True
def serialDataRcv():
while not bbq.closing:
line = ser.readline()
rcvdTime = time.time()
temps = str(line, 'ascii').split(" ")
temps = temps[1:-1]
for j, x in enumerate(temps):
bbq.temps[j].append(float(x))
bbq.plotTimes.append(rcvdTime)
def main():
sendThread = Thread(target = serialDataPump)
receiveThread = Thread(target = serialDataRcv)
sendThread.start()
receiveThread.start()
# sc.runUI()
control.runControl() #blocks until user closes window
bbq.closing = True
time.sleep(2)
exit()
if __name__ == '__main__':
main()
## testSerMain()
However, I'd like to add a SEPARATE tkinter window that just has the most recent data on it, a close button, etc. I can get that window to come up, and show data initially, but none of the other threads run. (and nothing works when I try to run the window and the plot at the same time.)
#-------------------------------------------------------------------------------
# Name: BBQ Display/Control
# Purpose: displays current temp data, and control options
#-------------------------------------------------------------------------------
import tkinter as tk
import tkinter.font
import my_globals as bbq
import threading
fontSize = 78
class BBQControl(tk.Tk):
def __init__(self,parent):
tk.Tk.__init__(self,parent)
self.parent = parent
self.labelFont = tkinter.font.Font(family='Helvetica', size=int(fontSize*0.8))
self.dataFont = tkinter.font.Font(family='Helvetica', size=fontSize, weight = 'bold')
self.makeWindow()
def makeWindow(self):
self.grid()
btnClose = tk.Button(self,text=u"Close")
btnClose.grid(column=1,row=5)
lblFood = tk.Label(self,anchor=tk.CENTER, text="Food Temps", \
font = self.labelFont)
lblFood.grid(column=0,row=0)
lblPit = tk.Label(self,anchor=tk.CENTER, text="Pit Temps", \
font = self.labelFont)
lblPit.grid(column=1,row=0)
self.food1Temp = tk.StringVar()
lblFoodTemp1 = tk.Label(self,anchor=tk.E, \
textvariable=self.food1Temp, font = self.dataFont)
lblFoodTemp1.grid(column=0,row=1)
#spawn thread to update temps
updateThread = threading.Thread(target = self.updateLoop)
updateThread.start()
def updateLoop(self):
self.food1Temp.set(str(bbq.temps[1][-1]))
def runControl():
app = BBQControl(None)
app.title('BBQ Display')
app.after(0, app.updateLoop)
app.mainloop()
bbq.closing = True
if __name__ == '__main__':
runControl()
Your title sums up the problem nicely: Tkinter doesn't play well with threads. That's not a question, that's the answer.
You can only access tkinter widgets from the same thread that created the widgets. If you want to use threads, you'll need your non-gui threads to put data on a queue and have the gui thread poll the queue periodically.
One way of getting tkinter to play well with threads is to modify the library so all method calls run on a single thread. Two other questions deal with this same problem: Updating a TKinter GUI from a multiprocessing calculation and Python GUI is not responding while thread is executing. In turn, the given answers point to several modules that help to solve the problem you are facing. Whenever I work with tkinter, I always use the safetkinter module in case threads appear to be helpful in the program.