2022-05-15 19:48:22 +00:00
from ui import Frame
2022-08-08 06:47:03 +00:00
from read_arduino import *
2022-08-08 22:51:36 +00:00
from serial_plotter import *
2022-08-18 09:56:58 +00:00
from multiprocessing import *
import os , sys , json , wx , importlib , warnings
2022-05-15 19:48:22 +00:00
import serial . tools . list_ports
import numpy as np
2023-05-01 00:57:42 +00:00
import matplotlib . pyplot as plt
2022-05-15 19:48:22 +00:00
2022-08-08 06:47:03 +00:00
warnings . filterwarnings ( " ignore " , category = DeprecationWarning )
2022-08-17 00:12:25 +00:00
# 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
2022-08-18 09:56:58 +00:00
2022-08-17 00:12:25 +00:00
pyi_splash . update_text ( ' UI Loaded ... ' )
pyi_splash . close ( )
2022-05-15 19:48:22 +00:00
HEIGHT = 800
WIDTH = 800
2023-05-01 00:57:42 +00:00
#global ani, t1
2022-08-08 22:51:36 +00:00
2023-03-04 05:37:01 +00:00
# TODO: add the option to change the font size of the graph
2022-05-15 19:48:22 +00:00
def main ( ) :
#################### USER INPUTS ###########################
2022-08-18 09:56:58 +00:00
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
2022-05-15 19:48:22 +00:00
input_voltage = float ( frame . input_voltage . GetValue ( ) )
2023-02-07 06:57:05 +00:00
# bit_rate = float(frame.adjusted_volt.GetValue())
2022-05-15 19:48:22 +00:00
port = frame . dev_list . GetValue ( )
2023-02-07 06:57:05 +00:00
# typical window size: 50
2022-05-15 19:48:22 +00:00
window_size = int ( frame . m_textCtrl26 . GetValue ( ) )
2023-03-07 05:15:07 +00:00
font_size = int ( frame . fontSize . GetValue ( ) )
2022-05-15 19:48:22 +00:00
#################### 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...
2023-05-07 21:37:04 +00:00
dat_folder = frame . dir . GetPath ( )
2022-05-15 19:48:22 +00:00
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 " )
2022-08-18 09:56:58 +00:00
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
2022-05-15 19:48:22 +00:00
if not ( len ( resistors ) == 5 ) :
raise ValueError ( f " expecting 5 resistor values, but got { len ( resistors ) } !!! " )
2023-03-07 05:15:07 +00:00
gen_settings ( resistors , input_voltage , port , filename , window_size , font_size , delay )
2022-05-15 19:48:22 +00:00
2023-03-07 05:15:07 +00:00
def gen_settings ( resistors , input_voltage , port , filename , window_size , font_size , delay ) :
2022-05-15 19:48:22 +00:00
"""
2023-04-17 03:17:45 +00:00
export all inputs from main ( ) to a . json file . We have everything stored in a . json file instead of having the program to call
the values from the UI directly because 1. we dont have to import the ui . py all the time ( so we can freely change ui . py to whatever
else and only need to modify this one ) and 2. we can have other program to access one variable from the ui at the same time . This adds
flexibility to the program . We parameterize all the value for the same reason .
2022-08-18 09:56:58 +00:00
: 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
2022-05-15 19:48:22 +00:00
"""
name = " settings.json "
settings = { }
settings [ " refRes " ] = resistors
settings [ " sensor_ports " ] = np . where ( np . array ( resistors ) > 0 ) [ 0 ] . tolist ( )
2022-08-08 06:47:03 +00:00
settings [ " v_in " ] = input_voltage
2022-05-15 19:48:22 +00:00
settings [ " port " ] = port
2023-02-07 06:57:05 +00:00
# settings["resolution"] = bits
2022-05-15 19:48:22 +00:00
settings [ " winSize " ] = window_size
2023-03-07 05:15:07 +00:00
settings [ " fontSize " ] = font_size
2022-05-15 19:48:22 +00:00
settings [ " file_name " ] = filename
settings [ " delay " ] = delay
2023-03-06 06:14:29 +00:00
settings [ " font_size " ] = frame . fontSize . GetValue ( )
2023-05-01 00:57:42 +00:00
global sensors
sensors = len ( settings [ ' sensor_ports ' ] )
2023-04-14 11:20:34 +00:00
# TODO: get the input voltage from the UI and save it to the settings.json
2022-05-15 19:48:22 +00:00
open ( name , ' w ' ) . write ( json . dumps ( settings , indent = 4 ) )
open ( filename , " a " , newline = " " , encoding = " utf-8 " )
2022-08-12 07:33:27 +00:00
def run ( e ) :
"""
2023-04-17 03:17:45 +00:00
run the read_arduino . py and Serial Plotter in parallel , but the plotter is in the main thread and other one can be in a separate thread , since
matplot ' s FuncAnimation doesn ' t like to not be in the main thread ( Big L for them . . )
2022-08-12 07:33:27 +00:00
"""
2023-05-01 00:57:42 +00:00
global ani_1 , t1 , ani_2 , ani_3 , ani_4 # the variables to call the plotter and the read_arduino.py, we want them to get tossed around between functions and even programs
2022-08-12 07:33:27 +00:00
main ( )
if ' t1 ' in globals ( ) :
t1 . terminate ( ) # end the previous serial reads, if there is any
2022-08-18 09:56:58 +00:00
2022-08-12 07:33:27 +00:00
# place the read() function from read_arduino into another process to run it in background
t1 = Process ( target = read , args = ( ) )
t1 . start ( )
2022-08-17 00:12:25 +00:00
# run the plotter. Note that we should not put the plotter class, or function, in another process since
2022-08-12 07:33:27 +00:00
# matplot's FuncAnimation doesn't like that
2022-08-18 09:56:58 +00:00
plotter = SerialPlotter ( frame )
2023-05-01 00:57:42 +00:00
if ( sensors == 1 ) :
ani_1 = plotter . plotting_1 ( )
elif ( sensors == 2 ) :
ani_1 = plotter . plotting_1 ( )
ani_2 = plotter . plotting_2 ( )
elif ( sensors == 3 ) :
ani_1 = plotter . plotting_1 ( )
ani_2 = plotter . plotting_2 ( )
ani_3 = plotter . plotting_3 ( )
elif ( sensors == 4 ) :
ani_1 = plotter . plotting_1 ( )
ani_2 = plotter . plotting_2 ( )
ani_3 = plotter . plotting_3 ( )
ani_4 = plotter . plotting_4 ( )
#plotter.fig_1.show()
#plotter.fig_2.show()
plt . show ( )
2022-05-15 19:48:22 +00:00
2022-08-12 07:33:27 +00:00
frame . btLaunch . SetLabelText ( " Plot " ) # change the text on the "launch" button to "plot"
2022-05-15 19:48:22 +00:00
if not frame . show_msg . GetValue ( ) :
2022-08-08 06:47:03 +00:00
frame . Hide ( )
2022-07-17 20:29:44 +00:00
2022-05-15 19:48:22 +00:00
if __name__ == ' __main__ ' :
2022-08-18 09:56:58 +00:00
console_title = ' Program Crashed! Here are the details ' # this is there for now to redirect any errors
2022-08-17 00:12:25 +00:00
# Acknowledgement: https://stackoverflow.com/a/27694505
if sys . platform . startswith ( ' win ' ) :
# On Windows calling this function is necessary.
freeze_support ( )
2022-08-18 09:56:58 +00:00
app = wx . App ( useBestVisual = True )
2022-05-15 19:48:22 +00:00
frame = Frame ( None )
2022-08-18 09:56:58 +00:00
app . SetTopWindow ( frame )
# set where the uncaught errors should be displayed
console = wx . PyOnDemandOutputWindow ( console_title )
console . SetParent ( frame )
sys . stderr = console
2023-04-17 03:17:45 +00:00
# instrad of print(), add something similar `sys.stdout = console` (without the two `) to print to allow us redirect the output elsewhere
# BUT it only redirects the print() in this program, not the print() on other modules, like read_arduino.py
print ( " this is an Beta version of the design. This debug terminal will be gone on official release \n " )
2023-04-14 11:20:34 +00:00
ports = [ f " { comport . device } : { comport . description } " for comport in serial . tools . list_ports . comports ( ) ] # get all available ports
2022-08-17 00:12:25 +00:00
frame . dev_list . AppendItems ( ports )
2023-04-14 11:20:34 +00:00
frame . SetTitle ( " SeeDatResistance - Beta 0.1.2 " )
2022-05-15 19:48:22 +00:00
frame . btLaunch . Bind ( wx . EVT_BUTTON , run )
2022-08-08 06:47:03 +00:00
if os . path . isfile ( " settings.json " ) :
2022-08-18 09:56:58 +00:00
try :
2023-04-17 03:17:45 +00:00
print ( " Found existing settings.json, auto-fill previous inputs! \n " )
2022-08-18 09:56:58 +00:00
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 " ] ) )
2023-02-07 06:57:05 +00:00
# frame.adjusted_volt.SetValue(str(settings["resolution"]))
2022-08-18 09:56:58 +00:00
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
2022-05-15 19:48:22 +00:00
frame . Show ( )
app . MainLoop ( )
2022-08-12 07:33:27 +00:00
if ' t1 ' in globals ( ) :
2022-08-17 00:12:25 +00:00
t1 . terminate ( ) # gracefully end the read_arduino process if it has been started