Read-Sensor-Resistances/serial_plotter.py

98 lines
4.4 KiB
Python
Raw Normal View History

2022-08-08 22:51:36 +00:00
from datetime import datetime
2022-08-18 09:56:58 +00:00
from matplotlib.animation import FuncAnimation
import os, json, traceback, wx
2022-08-08 22:51:36 +00:00
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import decimal
2022-12-15 23:22:33 +00:00
from itertools import islice
2022-08-18 09:56:58 +00:00
matplotlib.use("WXAgg") # for JetBrains IDE to force use wxPython as backend UI for plotting
2022-08-08 22:51:36 +00:00
class SerialPlotter:
2022-08-18 09:56:58 +00:00
def __init__(self, parent: wx.Frame = None) -> None:
"""
Dynamically plot a graph with moving window, if a finite window size set by the user. It reads the .csv file
read_arduino generated, use the last line as the latest data and update the graph accordingly. It will plot
multiple lines on the graph if there are more than one values on the last row of the .csv file
:param parent: optional, parent frame
"""
self.parent = parent
2022-08-08 22:51:36 +00:00
self.settings = json.load(open('settings.json', 'r'))
self.sensors = len(self.settings['sensor_ports'])
2022-08-18 09:56:58 +00:00
self.windowsize = self.settings['winSize']
2022-08-08 22:51:36 +00:00
self.delay = self.settings["delay"] / 1000
2022-08-18 09:56:58 +00:00
2022-08-08 22:51:36 +00:00
self.colors = ['blue', 'orange', 'green', 'yellow']
2022-08-18 09:56:58 +00:00
# TODO: make the figure size an UI option and pass into the settings.json
self.fig, self.axs = plt.subplots(1, 1, figsize=(9, 6))
2022-08-08 22:51:36 +00:00
2022-08-18 09:56:58 +00:00
self.fig.canvas.mpl_connect('close_event', self.event_close)
2022-08-08 22:51:36 +00:00
self.timeStamps = {}
self.sensorsData = {}
2022-08-18 09:56:58 +00:00
self.timeElapsed = 0 # time have passed since the graph started, in seconds
2022-08-08 22:51:36 +00:00
for i in range(self.sensors):
2022-08-18 09:56:58 +00:00
self.timeStamps[i] = ['']
self.sensorsData[i] = [0]
2022-08-08 22:51:36 +00:00
2022-08-18 09:56:58 +00:00
def event_close(self, event) -> None:
"""
Actiions need to be executed when the graph has closed. Start a new .csv file to get read for new graph and
bring back the UI, if exist
"""
2022-08-08 22:51:36 +00:00
file = self.settings["file_name"]
wx.MessageBox(f"File has saved as {os.path.split(file)[1]} under {os.path.split(file)[0]} directory!\n")
2022-08-18 09:56:58 +00:00
if self.parent:
self.parent.Show()
2022-08-08 22:51:36 +00:00
2022-08-18 09:56:58 +00:00
def animation(self, t: int) -> None:
"""
render a frame of the animated graph
"""
2022-08-08 22:51:36 +00:00
try:
plt.cla() # clear previous frame
2022-08-18 09:56:58 +00:00
# read the last line from the .csv file, the data start from the second column so omit index #0
2022-08-08 22:51:36 +00:00
file = open(self.settings["file_name"], "r")
2022-12-15 23:22:33 +00:00
ndata = np.array([np.asarray(line.split(", ")[1:], dtype=np.float32) for line in islice(file, 1, None)])#read from the second row of the file ignoring headers
2022-08-08 22:51:36 +00:00
if len(ndata) > 0:
row = ndata[-1]
i = 0
while i < self.sensors:
2022-08-18 09:56:58 +00:00
# shift all data left by 1 index, pop out the leftmost value, if windowsize is not 0 (infinite)
# TODO: make sure the two lists have the same size
# if self.windowsize > 0 and len(self.timeStamps[i]) > self.windowsize
if 0 < self.windowsize < len(self.timeStamps[i]):
2022-08-08 22:51:36 +00:00
self.timeStamps[i].pop(0)
self.sensorsData[i].pop(0)
2022-08-18 09:56:58 +00:00
# self.timeStamps[i].append(datetime.now().strftime('%H:%M:%S')) # version 1
# version 2, if we decide to go with this one change the list type to list[int] from list[str]
self.timeStamps[i].append(str(self.timeElapsed))
2022-08-08 22:51:36 +00:00
self.sensorsData[i].append(row[i])
# plot a line
# round the number to scientific notation
self.axs.plot(self.timeStamps[i],self.sensorsData[i], color=self.colors[i],
label=f'sensor {i + 1}, latest: {np.format_float_scientific(self.sensorsData[i][-1], precision = 2)} $\Omega$')
2022-08-18 09:56:58 +00:00
self.axs.set_xlabel('Time (seconds)')
self.axs.set_ylabel(u'Resistance ($\Omega$)')
2022-08-08 22:51:36 +00:00
i += 1
2022-08-18 09:56:58 +00:00
self.timeElapsed += 1 # increment time
2022-08-08 22:51:36 +00:00
# Acknowledgement: https://stackoverflow.com/a/13589144
handles, labels = self.axs.get_legend_handles_labels()
by_label = dict(zip(labels, handles))
self.axs.legend(by_label.values(), by_label.keys(), loc='best')
except:
traceback.print_exc()
2022-08-12 07:33:27 +00:00
def plotting(self) -> FuncAnimation:
2022-08-18 09:56:58 +00:00
""" animate the dynamic plot """
ani = FuncAnimation(self.fig, self.animation, blit=False, interval=self.delay * 1000)
2022-08-08 22:51:36 +00:00
return ani