from ui import Frame from read_arduino import * from serial_plotter import * from multiprocessing import * import os, sys, json, wx, importlib, warnings import serial.tools.list_ports import numpy as np warnings.filterwarnings("ignore", category=DeprecationWarning) # acknowledgement: https://stackoverflow.com/a/68666505. handles splash screens if '_PYIBoot_SPLASH' in os.environ and importlib.util.find_spec("pyi_splash"): import pyi_splash pyi_splash.update_text('UI Loaded ...') pyi_splash.close() HEIGHT = 800 WIDTH = 800 global ani, t1 # TODO: add the option to change the font size of the graph def main(): #################### USER INPUTS ########################### r_ref = np.array( [frame.r_ref_1.GetValue(), frame.r_ref_2.GetValue(), frame.r_ref_3.GetValue(), frame.r_ref_4.GetValue(), 0]) # resisters for each An port, where n is an integer from 0-3. Use 0 if none. in Ohms r_ref[r_ref == ''] = '0' # correcting the emply values resistors = r_ref.astype(np.float32).tolist() # convert string to numbers input_voltage = float(frame.input_voltage.GetValue()) # bit_rate = float(frame.adjusted_volt.GetValue()) port = frame.dev_list.GetValue() # typical window size: 50 window_size = int(frame.m_textCtrl26.GetValue()) font_size = int(frame.fontSize.GetValue()) #################### END USER INPUTS ######################## # filename, disable customize name but should inform the user the current name of the data file on the front end # alternatively, could be default input if no other input given. But if there is might have problem... dat_folder = "RecordedData" os.makedirs(dat_folder, exist_ok=True) filename = os.path.join(os.getcwd(), dat_folder, f"{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.csv") delay = 1000 # millisec per data point, defined in the firmware # for now we have 5 reference resistors, there is an extra one (we set 4 on the UI) just in case if needed in the future if not (len(resistors) == 5): raise ValueError(f"expecting 5 resistor values, but got {len(resistors)}!!!") gen_settings(resistors, input_voltage, port, filename, window_size, font_size, delay) def gen_settings(resistors, input_voltage, port, filename, window_size, font_size, delay): """ export all inputs from main() to a .json file :param resistors: list of reference resistances of the sensors :param input_voltage: Vin of the voltage divider :param bits: ADC resolution for the board. Usualy 12-bits for ESP32 (and 10 bits for Arduino Mega) :param port: serial port the board is connected to on the computer :param filename: file name and absolute directory which the serial data will be recorded to :param window_size: numbers of x values to display on the graph, or the x interval :param delay: amount of time to wait for a task's next start. 1000 ms for now to be consistent with the firmware """ name = "settings.json" settings = {} settings["refRes"] = resistors settings["sensor_ports"] = np.where(np.array(resistors) > 0)[0].tolist() settings["v_in"] = input_voltage settings["port"] = port # settings["resolution"] = bits settings["winSize"] = window_size settings["fontSize"] = font_size settings["file_name"] = filename settings["delay"] = delay settings["font_size"] = frame.fontSize.GetValue() open(name, 'w').write(json.dumps(settings, indent=4)) open(filename, "a", newline="", encoding="utf-8") def run(e): """ run the read_arduino.py and Serial Plotter in parallel """ global ani, t1 # the variables to call the plotter and the read_arduino.py, we want them to get tossed around between functions and even programs main() if 't1' in globals(): t1.terminate() # end the previous serial reads, if there is any # place the read() function from read_arduino into another process to run it in background t1 = Process(target=read, args=()) t1.start() # run the plotter. Note that we should not put the plotter class, or function, in another process since # matplot's FuncAnimation doesn't like that plotter = SerialPlotter(frame) ani = plotter.plotting() plotter.fig.show() frame.btLaunch.SetLabelText("Plot") # change the text on the "launch" button to "plot" if not frame.show_msg.GetValue(): frame.Hide() if __name__ == '__main__': console_title = 'Program Crashed! Here are the details' # this is there for now to redirect any errors # Acknowledgement: https://stackoverflow.com/a/27694505 if sys.platform.startswith('win'): # On Windows calling this function is necessary. freeze_support() app = wx.App(useBestVisual=True) frame = Frame(None) app.SetTopWindow(frame) # set where the uncaught errors should be displayed console = wx.PyOnDemandOutputWindow(console_title) console.SetParent(frame) sys.stderr = console sys.stdout.write("this is an alpha version of the design. This debug terminal will be gone on official release\n") ports = [comport.device for comport in serial.tools.list_ports.comports()] # get all available ports frame.dev_list.AppendItems(ports) frame.SetTitle("SeeDatResistance - Beta 0.1.1") frame.btLaunch.Bind(wx.EVT_BUTTON, run) if os.path.isfile("settings.json"): try: sys.stdout.write("Found existing settings.json, auto-fill previous inputs!\n") settings = json.load(open('settings.json', 'r')) frame.r_ref_1.SetValue(str(settings["refRes"][0])) frame.r_ref_2.SetValue(str(settings["refRes"][1])) frame.r_ref_3.SetValue(str(settings["refRes"][2])) frame.r_ref_4.SetValue(str(settings["refRes"][3])) frame.input_voltage.SetValue(str(settings["v_in"])) # frame.adjusted_volt.SetValue(str(settings["resolution"])) frame.m_textCtrl26.SetValue(str(settings["winSize"])) if settings["port"] in ports: # auto-select device port if exist frame.dev_list.SetValue(settings["port"]) except json.decoder.JSONDecodeError: # invalid settings file, ignore pass frame.Show() app.MainLoop() if 't1' in globals(): t1.terminate() # gracefully end the read_arduino process if it has been started