From 8ca2fe39d0657ec153d1e4c2d3194dd390687840 Mon Sep 17 00:00:00 2001 From: Ayzen Date: Tue, 17 Feb 2026 22:20:47 +0300 Subject: [PATCH] added initial config --- __pycache__/device_commands.cpython-312.pyc | Bin 25112 -> 25281 bytes .../device_interaction.cpython-312.pyc | Bin 10164 -> 9786 bytes __pycache__/gui.cpython-312.pyc | Bin 21784 -> 23170 bytes _device_main.py | 91 ++++++- device_commands.py | 6 +- device_interaction.py | 248 ++++++++++-------- gui.py | 6 +- 7 files changed, 225 insertions(+), 126 deletions(-) diff --git a/__pycache__/device_commands.cpython-312.pyc b/__pycache__/device_commands.cpython-312.pyc index cb7767e928627f78b2b5a19c6b944f6a97c5a911..11be430620df9ceee82daa235398fc6d5e9f3439 100644 GIT binary patch delta 1213 zcmYk*du&rx90%~8cIS?6>lo8T5Z1Zd%Q1MX zN6>{aBSSXxTO!e5A^~+08OsqApJ78K;)B3^a3PAupyFG6;~$>y)l6^F&;9*==W*}t zIcMilcI0*DIqvb~IMns(;i1);QIB-G9M_t1+({YkG~)xtJYap@yWP>Sc7gq>^P*uYR3t%7*LdkZw8y zm2^RGo*|s7qqtWQGq@;2vL0-p8+|jijR-f<8sC*UJt{n>e(7zr!x!`C<`0A(4E2Y1 zE;*U&J)1Y}L}B^UzLE0b@&keAL!%)XK2;H!$cxG|z98d$YTQLRz0B)m>#4?H$A?tm z51H|gxY-c-%@)>2Tg?*IOMA@o1*%_yufPRhcmD{{Y4GpBS&h~FN0pzDw>0{B!5%d21y6#f zXnRxL0<4elgJG}_JOgHf3UCeB0_G?TZfSS5iMGzP=tXTSxnr~WUn;yP=f~!N(4V$05E&_#OD3 zUT)Pj`v`wRpSRx7xEa+R&hVMRY(Eyr$)v%djTZc0D^ delta 1102 zcmYMzd2GvJ7zgma?cN_Voep!&W^Ff9WY*fzrKnM{1l=Sv$vUb;WUlc}VkQg0{%}v8 zKPIkZal{eEUg8L$n8g|QQNa*b=8hvV5%2f6geLv`p67Y5U;DmqxQT7P$jn#G=1vCn z*;Ko#tlJT@G@3?MR?7>3VcMI#P z_l*+&=e$6@JURTb>JgwK&q5ZKmpm4er3s^BK_ggAuSd99GxhV@G>O7#8ta|l(3cD& z*O$D4NMH9VqHEwfxIt&Usp^-#^tyRO70P7t$W{|uL!Etgc9b%FQ!V-%Jx1sDS!jo^ zcenek^{2MS8sB>Mvph(EqDj?p$`x}1nrYhQ+_7@tnwT3 zPktJ=>_pQpup8{5EWc+I_C=(Dynqn0-`Umkg z)$&EY@OwCGrKF--wt^ap#&gaL220~4Rn4YhCAC^dm5XS)U6RM#)ICtB)nnRiG&_(K zUyJY?u!HYlBW({@*#FQUCw| diff --git a/__pycache__/device_interaction.cpython-312.pyc b/__pycache__/device_interaction.cpython-312.pyc index ae848eb3d35d37284139707f8908762a6bc2e6f4..1abc2f412b2073f634d7565617cba8cc3e9da4a7 100644 GIT binary patch delta 4504 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 ORme2^5S&_Q-0t6mS7H_b literal 10164 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 Qsvs|u&%Q&Fb$HzW0lrk~6#xJL diff --git a/__pycache__/gui.cpython-312.pyc b/__pycache__/gui.cpython-312.pyc index 5b6048b62bade7180d03efc3a988afbeb0649650..a11ad81ac1d5c0be61246da1465243b8eb656dd5 100644 GIT binary patch delta 5341 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 J4$^Z0{{w+8237z7 delta 4239 zcmbVPdrVu`8TYvuV{E{LmtXjW4TNV>AT&Gz1cEV`KvWD%Xe2{8jsr7~T$}QUfrO-O z)^%x|({^basg=5iQoK!**MFL=nyM*Uv_FEjXqBk4hZJdlbkn*vTBS*ycD{S>acuKg z)hqGych2|we&2V$d(ORw_4m=u_fXF7va?k(dR4App4{w<Q+pow*kKzuBo%krlt|e5_84IBH3}hlvrIFlI zM#y~Tomj6b!lA<0Cs}uqN>6j~03)%DoF1gsP&1AGebH(r>&T$ycLX^7s65Br@}MWxiYh%$o(}r~ouGix`vC9?*)4 zq6T@kB9k;0T95&F2Z|XFYeHsbpXEYUW`|0cMQ0u4W?^Zf+P*U8#aYYL4DBb%BHIqy zm5i69U7axNAb}!NHWy0mPA%hX>rM`BWiByjS$7WZ%KSiJ-lfd1(;ck)rro)#+pRkW zsXLAOZ`z$V^(IZ4U$(73b@97y5LQnkIj_nmdV^V7Gl!G>4XUsVm%suI<&swokPc%eZEv5kN)l?_F0@|~`n{0}Wf!x&ZOL@9S`2bmizPYIUMwYoifSaG zTocRInG$}@1mQQSUwDM5h@7slNlSXr#A@z018c<0%88hza!9s0Azg!%F3ZBwxsuaC zO^vKe7N&OJ!qnUrma)Ssbjiwu`nL*#FoMXOF!cN;U<{^tA7s^vhF?tfKW|EjY%i&?N>Cj7$$Fe^) zFnc61kXS3Ew^`Cw_>~m)%gx{==(M3rhNsk;xJx|US#XkkNonL#OHGzwut=(>v=dGXW&a= zMA!EEhC-ZOQG^v!uL;lYGG1fZkSweil2u$G|9Qh6yj{`)}b(>JpI#U&C6LeL!og# z)U`Ab@YPhL@gU7lvcP3EbjeQyrX>ioPzSXGM@j+Il{UhmJ1*>2LIKe^K^Gu6WUP$#wPg1G(W*p6w5L_KWVAyFE7z`N`?dc8a$; zS1B61j#HfMTFvky{i5Qrj?8wolbT~nX>2CfoKB@hbUMhNoIQq8aZo8U6u)hH!xU|Q zpl{jKwSHFRC!Zc;It|3u+0Mda$C*ehVf<)ZeMfc9Rn0|LI^3!JZdT}n}VgrhZCNT3e{uJ^f0 z#OX6!YRU;t^ql3$UtL8c*R7xdS~n<<33%J>lIjg_3tr*;B%9PDqfi9&s8sVUrP zqGy2I>;EuoP#kT_3mYe4(Vqn_%n6hg7-?3y zN1A=Hya6rB)T(oiql?bR22wY80M*CN3{J>ZaOPd+tX=#xxj$59=%&N@bPCqZUv6IK zr>MolL6h$#8c&g81csqwZp1}dH zr~9Obp9j|!fF*$E0pL5v+W-~;t^#}q0ABb!d=vQZ0(=kPnoKq%<6i+2-c9^Tc>C7 ze8AVn|CY|dcRLt~eQ+`z(Rb3XPnDB@oEp}CuCgQT=Y8lLhw{D{NzXw=g24X(qh*2Q diff --git a/_device_main.py b/_device_main.py index 9d56c0c..e269164 100644 --- a/_device_main.py +++ b/_device_main.py @@ -3,6 +3,7 @@ import json import math import socket import subprocess +import time import device_interaction as dev @@ -23,6 +24,14 @@ INITIAL_TEMPERATURE_2 = 28.9 # Set initial temperature for Laser 2 in Celsius: f INITIAL_CURRENT_1 = 33 # 64.0879 max # Set initial current for Laser 1, in mA INITIAL_CURRENT_2 = 35 # 64.0879 max # Set initial current for Laser 2, in mA +AD9833_FREQ_DEFAULT_KHZ = 125.0 +AD9833_MCLK_DEFAULT_MHZ = 20.0 +DS1809_MAX_STEP = 63 +DS1809_DEFAULT_STEP = 0 +DS1809_INIT_HOME_PULSES = 64 +DS1809_INIT_PULSE_MS = 2 +DS1809_INIT_STARTUP_DELAY_S = 0.35 + #### ---- Functions def start_task(prt): @@ -73,6 +82,38 @@ def shorten(i): return "{:.2f}".format(round(i, 2)) +def clamp_int(value, min_value, max_value): + if value < min_value: + return min_value + if value > max_value: + return max_value + return value + + +def format_ds1809_status(step): + return f"{step}/{DS1809_MAX_STEP} шагов" + + +def initialize_ds1809_position(prt, default_step): + default_step = clamp_int(int(default_step), 0, DS1809_MAX_STEP) + + # Give the STM32 side a short startup margin before DS1809 pulse traffic. + time.sleep(DS1809_INIT_STARTUP_DELAY_S) + + dev.send_ds1809_pulse(prt, uc=False, dc=True, + count=DS1809_INIT_HOME_PULSES, + pulse_ms=DS1809_INIT_PULSE_MS) + current_step = 0 + + if default_step > 0: + dev.send_ds1809_pulse(prt, uc=True, dc=False, + count=default_step, + pulse_ms=DS1809_INIT_PULSE_MS) + current_step = default_step + + return current_step + + def set_initial_params(): params = {} params['Temp_1'] = INITIAL_TEMPERATURE_1 # Initial temperature for Laser 1 @@ -108,9 +149,11 @@ def set_initial_params(): params['RampSramMode'] = False params['RampSramSamples'] = '' params['RampSramAmp'] = '' - params['Ad9833Freq'] = '' - params['Ad9833Mclk'] = '25' + params['Ad9833Freq'] = str(AD9833_FREQ_DEFAULT_KHZ) + params['Ad9833Mclk'] = str(AD9833_MCLK_DEFAULT_MHZ) params['Ad9833Triangle'] = True + params['DS1809Step'] = DS1809_DEFAULT_STEP + params['DS1809Status'] = format_ds1809_status(DS1809_DEFAULT_STEP) return params def update_data_lists(): @@ -139,12 +182,29 @@ if __name__ == "__main__": # dev.request_state(prt) dev.send_control_parameters(prt, params) - saved_data.append(dev.request_data(prt)) - draw_data.append(saved_data[0]) - + window = gui.setup_gui(params) axes_signs = gui.sign_axes(window) - + + ds1809_step = initialize_ds1809_position(prt, params['DS1809Step']) + params['DS1809Step'] = ds1809_step + params['DS1809Status'] = format_ds1809_status(ds1809_step) + window['-DS1809Status-'].update(params['DS1809Status']) + + initial_data = None + for _ in range(20): + initial_data = dev.request_data(prt) + if isinstance(initial_data, dict): + break + time.sleep(0.05) + + if not isinstance(initial_data, dict): + print('Error: initial DATA packet not received. Closing program...') + exit(1) + + saved_data.append(initial_data) + draw_data.append(initial_data) + current_and_temperature_settings_available = True disableStartButton = False @@ -492,22 +552,33 @@ if __name__ == "__main__": sram_mode=sram_mode, sram_samples=sram_samples, sram_amplitude=sram_amplitude) elif event == '-StartRamp9833-': - freq_hz = parse_optional_float(values.get('-AD9833Freq-')) + freq_khz = parse_optional_float(values.get('-AD9833Freq-')) + freq_hz = int(round(freq_khz * 1000.0)) if freq_khz is not None else None mclk_mhz = parse_optional_float(values.get('-AD9833Mclk-')) mclk_hz = mclk_mhz * 1e6 if mclk_mhz is not None else None triangle = values.get('-AD9833Triangle-', True) dev.start_ad9833_ramp(prt, freq_hz=freq_hz, mclk_hz=mclk_hz, triangle=triangle, enable=True) elif event == '-DS1809UC-': - dev.send_ds1809_pulse(prt, uc=True, dc=False) + dev.send_ds1809_pulse(prt, uc=True, dc=False, count=1, pulse_ms=DS1809_INIT_PULSE_MS) + ds1809_step = clamp_int(ds1809_step + 1, 0, DS1809_MAX_STEP) + params['DS1809Step'] = ds1809_step + params['DS1809Status'] = format_ds1809_status(ds1809_step) + window['-DS1809Status-'].update(params['DS1809Status']) elif event == '-DS1809DC-': - dev.send_ds1809_pulse(prt, uc=False, dc=True) + dev.send_ds1809_pulse(prt, uc=False, dc=True, count=1, pulse_ms=DS1809_INIT_PULSE_MS) + ds1809_step = clamp_int(ds1809_step - 1, 0, DS1809_MAX_STEP) + params['DS1809Step'] = ds1809_step + params['DS1809Status'] = format_ds1809_status(ds1809_step) + window['-DS1809Status-'].update(params['DS1809Status']) elif event == '-StopCycle-': window['-StopCycle-'].update(disabled = True) current_and_temperature_settings_available = True stop_task(prt) elif event == TIMEOUT_KEY: data = dev.request_data(prt) - + if not isinstance(data, dict): + continue + update_data_lists() window['-TOUT_1-'].update(gui.READ_TEMPERATURE_TEXT+' 1: '+shorten(data['Temp_1'])+' C') diff --git a/device_commands.py b/device_commands.py index 7611d3e..14a0ffa 100644 --- a/device_commands.py +++ b/device_commands.py @@ -27,7 +27,7 @@ AD9102_SRAM_HOLD_DEFAULT = 1 AD9102_SRAM_AMP_DEFAULT = 8191 AD9833_FLAG_ENABLE = 0x0001 AD9833_FLAG_TRIANGLE = 0x0002 -AD9833_MCLK_HZ_DEFAULT = 25_000_000 +AD9833_MCLK_HZ_DEFAULT = 20_000_000 DS1809_FLAG_UC = 0x0001 DS1809_FLAG_DC = 0x0002 DS1809_PULSE_MS_DEFAULT = 2 @@ -222,13 +222,15 @@ def get_STATE(prt): def get_DATA(prt): - ''' Get decoded state of the device in byte format (426 bytes). + ''' Get decoded state of the device in byte format (30 bytes). ''' print("Received "+str(prt.inWaiting())+" bytes.\n") if prt.inWaiting()!=GET_DATA_TOTAL_LENGTH: print("Error. Couldn't get DATA data.") print("receiven data len:", prt.inWaiting()) + if prt.inWaiting() > 0: + print("Flushing input data:", prt.read(prt.inWaiting())) return None out_bytes = prt.read(GET_DATA_TOTAL_LENGTH) diff --git a/device_interaction.py b/device_interaction.py index f331aba..ceb721e 100644 --- a/device_interaction.py +++ b/device_interaction.py @@ -1,118 +1,96 @@ - - import time - from datetime import datetime + import device_commands as cmd #### ---- Constants -WAIT_AFTER_SEND = 0.15 # Wait after sending command, before requesting input (in seconds). +WAIT_AFTER_SEND = 0.15 # Wait after sending command before requesting input (s). #### ---- High-level port commands -''' -def create_port_connection(): - prt = None - for port, _, _ in sorted(cmd.list_ports.comports()): - try: - prt = cmd.setup_port_connection(port=port, baudrate=115200, timeout_sec=1) - cmd.open_port(prt) - reset_port_settings(prt) - except: - prt.close() - continue - break - return prt -''' + def create_port_connection(): prt = None print() - ports = [] - for port, _,_ in sorted(cmd.list_ports.comports()): - ports.append(port) -#ONLY FOR LINUX!!! - have_ttyUSB = False - USB_ports = [] - for port in ports: - if "USB" in port: - USB_ports.append(port) - if len(USB_ports): - ports = USB_ports -# print("ports:", ports) + ports = [port for port, _, _ in sorted(cmd.list_ports.comports())] + # Linux-only preference: use USB UART ports first. + usb_ports = [port for port in ports if "USB" in port] + if usb_ports: + ports = usb_ports -# for port, _, _ in sorted(cmd.list_ports.comports()): for port in ports: try: print("PORT:", port) prt = cmd.setup_port_connection(port=port, baudrate=115200, timeout_sec=1) cmd.open_port(prt) reset_port_settings(prt) - except: - prt.close() + return prt + except Exception: + if prt is not None: + try: + prt.close() + except Exception: + pass continue - break - return prt + + return None +def _print_state_reply(state_bytes): + if state_bytes is None: + return False - -# def setup_connection(): -# prt = cmd.setup_port_connection() -# cmd.open_port(prt) -# return prt + status = state_bytes.hex() + print("Received: STATE. State status:", cmd.decode_STATE(status), "(" + cmd.flipfour(status) + ")") + print("") + return True def reset_port_settings(prt): - # Reset port settings and check status cmd.send_DEFAULT_ENABLE(prt) time.sleep(WAIT_AFTER_SEND) - status = cmd.get_STATE(prt).hex() - if status is not None: - print("Received: STATE. State status:", cmd.decode_STATE(status), "("+cmd.flipfour(status)+")") - print("") + return _print_state_reply(cmd.get_STATE(prt)) def request_state(prt): - # Request data cmd.send_STATE(prt) time.sleep(WAIT_AFTER_SEND) - status = cmd.get_STATE(prt).hex() - if status is not None: - print("Received: STATE. State status:", cmd.decode_STATE(status), "("+cmd.flipfour(status)+")") - print("") + return _print_state_reply(cmd.get_STATE(prt)) def send_control_parameters(prt, params): - # Send control parameters hexstring = cmd.encode_Input(params) - cmd.send_DECODE_ENABLE(prt,hexstring) + cmd.send_DECODE_ENABLE(prt, hexstring) time.sleep(WAIT_AFTER_SEND) - status = cmd.get_STATE(prt).hex() - if status is not None: - print("Received: STATE. State status:", cmd.decode_STATE(status), "("+cmd.flipfour(status)+")") - print("") - else: - print("") + return _print_state_reply(cmd.get_STATE(prt)) + def send_task_command(prt, sending_param): # Send task command (TASK_ENABLE state in firmware) hexstring = cmd.create_TaskEnableCommand(sending_param) - cmd.send_TASK_ENABLE(prt,hexstring) + cmd.send_TASK_ENABLE(prt, hexstring) time.sleep(WAIT_AFTER_SEND) - status = cmd.get_STATE(prt).hex() - if status is not None: - print("Received: STATE. State status:", cmd.decode_STATE(status), "("+cmd.flipfour(status)+")") - print("") - else: - print("") + return _print_state_reply(cmd.get_STATE(prt)) - -def start_ramp_max(prt, freq_hz=None, duty=None, saw_step=None, pat_period=None, pat_period_base=None, dac_clk_hz=None, triangle=True, sram_mode=False, sram_samples=None, sram_hold=None, sram_amplitude=None): +def start_ramp_max( + prt, + freq_hz=None, + duty=None, + saw_step=None, + pat_period=None, + pat_period_base=None, + dac_clk_hz=None, + triangle=True, + sram_mode=False, + sram_samples=None, + sram_hold=None, + sram_amplitude=None, +): # Start AD9102 sawtooth with configurable frequency/duty or SRAM ramp mode if sram_mode: if sram_hold is None: @@ -121,9 +99,14 @@ def start_ramp_max(prt, freq_hz=None, duty=None, saw_step=None, pat_period=None, if dac_clk_hz is None: dac_clk_hz = cmd.AD9102_DAC_CLK_HZ sram_samples = cmd.calc_sram_samples_for_freq(freq_hz, dac_clk_hz, sram_hold) - hexstring = cmd.create_AD9102_ramp_command(enable=True, triangle=triangle, sram_mode=True, - sram_samples=sram_samples, sram_hold=sram_hold, - sram_amplitude=sram_amplitude) + hexstring = cmd.create_AD9102_ramp_command( + enable=True, + triangle=triangle, + sram_mode=True, + sram_samples=sram_samples, + sram_hold=sram_hold, + sram_amplitude=sram_amplitude, + ) else: if pat_period_base is None: pat_period_base = cmd.AD9102_PAT_PERIOD_BASE_DEFAULT @@ -137,74 +120,113 @@ def start_ramp_max(prt, freq_hz=None, duty=None, saw_step=None, pat_period=None, pat_period = cmd.calc_pat_period_for_duty(saw_step, duty, pat_period_base, triangle) if pat_period is None: pat_period = cmd.AD9102_PAT_PERIOD_DEFAULT - hexstring = cmd.create_AD9102_ramp_command(saw_step, pat_period, pat_period_base, - enable=True, triangle=triangle) + hexstring = cmd.create_AD9102_ramp_command( + saw_step, + pat_period, + pat_period_base, + enable=True, + triangle=triangle, + ) + cmd.send_AD9102(prt, hexstring) time.sleep(WAIT_AFTER_SEND) - status = cmd.get_STATE(prt).hex() - if status is not None: - print("Received: STATE. State status:", cmd.decode_STATE(status), "("+cmd.flipfour(status)+")") - print("") - else: - print("") + return _print_state_reply(cmd.get_STATE(prt)) def start_ad9833_ramp(prt, freq_hz=None, mclk_hz=None, triangle=True, enable=True): if freq_hz is None: freq_hz = 0.0 - hexstring = cmd.create_AD9833_ramp_command(freq_hz=freq_hz, mclk_hz=mclk_hz, - enable=enable, triangle=triangle) + hexstring = cmd.create_AD9833_ramp_command( + freq_hz=freq_hz, + mclk_hz=mclk_hz, + enable=enable, + triangle=triangle, + ) cmd.send_AD9833(prt, hexstring) time.sleep(WAIT_AFTER_SEND) - status = cmd.get_STATE(prt).hex() - if status is not None: - print("Received: STATE. State status:", cmd.decode_STATE(status), "("+cmd.flipfour(status)+")") - print("") - else: - print("") + return _print_state_reply(cmd.get_STATE(prt)) + + +def _wait_for_min_bytes(prt, expected_len, timeout_s, poll_s=0.01): + deadline = time.time() + timeout_s + while time.time() < deadline: + waiting = prt.inWaiting() + if waiting >= expected_len: + return True + time.sleep(poll_s) + return prt.inWaiting() >= expected_len def send_ds1809_pulse(prt, uc=False, dc=False, count=1, pulse_ms=None): + if count is None or count <= 0: + count = 1 + if pulse_ms is None or pulse_ms <= 0: + pulse_ms = cmd.DS1809_PULSE_MS_DEFAULT + hexstring = cmd.create_DS1809_pulse_command(uc=uc, dc=dc, count=count, pulse_ms=pulse_ms) cmd.send_DS1809(prt, hexstring) - time.sleep(WAIT_AFTER_SEND) - status = cmd.get_STATE(prt).hex() - if status is not None: - print("Received: STATE. State status:", cmd.decode_STATE(status), "("+cmd.flipfour(status)+")") - print("") - else: - print("") + + # Firmware blocks while pulsing DS1809 lines: wait pulse train + safe margin. + pulse_train_time = (2.0 * float(count) * float(pulse_ms)) / 1000.0 + time.sleep(max(WAIT_AFTER_SEND, pulse_train_time + 0.35)) + + # Then poll shortly for STATE bytes; this avoids early read (0 bytes) on startup. + _wait_for_min_bytes(prt, expected_len=2, timeout_s=0.8) + + return _print_state_reply(cmd.get_STATE(prt)) def request_data(prt): - # Request data cmd.send_TRANS_ENABLE(prt) time.sleep(WAIT_AFTER_SEND) - data = cmd.get_DATA(prt).hex() - data_dict = [] - if data is not None: - data_dict = cmd.decode_DATA(data) - return data_dict + + data_bytes = cmd.get_DATA(prt) + if data_bytes is None: + return None + + return cmd.decode_DATA(data_bytes.hex()) def print_data(data): def shorten(i): return str(round(i, 2)) - print("Data from device (time: "+datetime.now().strftime("%H:%M:%S:%f")+"):") - print("Message Header:", data['Header'], " Message ID:", data['Message_ID']) - print("Photodiode Current 1 ("+str(len(data['I1']))+" values):", \ - shorten(data['I1']), shorten(data['I1'][1]), "...", \ - shorten(data['I1']), shorten(data['I1'][-1]), "mA") - print("Photodiode Current 2 ("+str(len(data['I2']))+" values):", \ - shorten(data['I2']), shorten(data['I2'][1]), "...", \ - shorten(data['I2']), shorten(data['I2'][-1]), "mA") - print("Laser Temperature 1:", shorten(data['Temp_1']), "C") - print("Laser Temperature 2:", shorten(data['Temp_2']), "C") - print("Temperature of external thermistor 1:", shorten(data['Temp_Ext_1']), "C") - print("Temperature of external thermistor 2:", shorten(data['Temp_Ext_2']), "C") - print("Voltages 3V3: "+shorten(data['MON_3V3'])+"V 5V1: "+shorten(data['MON_5V1'])+ \ - "V 5V2: "+shorten(data['MON_5V2'])+"V 7V0: "+shorten(data['MON_7V0'])+"V.") + print("Data from device (time: " + datetime.now().strftime("%H:%M:%S:%f") + "):") + print("Message Header:", data["Header"], " Message ID:", data["Message_ID"]) + print( + "Photodiode Current 1 (" + str(len(data["I1"])) + " values):", + shorten(data["I1"]), + shorten(data["I1"][1]), + "...", + shorten(data["I1"]), + shorten(data["I1"][-1]), + "mA", + ) + print( + "Photodiode Current 2 (" + str(len(data["I2"])) + " values):", + shorten(data["I2"]), + shorten(data["I2"][1]), + "...", + shorten(data["I2"]), + shorten(data["I2"][-1]), + "mA", + ) + print("Laser Temperature 1:", shorten(data["Temp_1"]), "C") + print("Laser Temperature 2:", shorten(data["Temp_2"]), "C") + print("Temperature of external thermistor 1:", shorten(data["Temp_Ext_1"]), "C") + print("Temperature of external thermistor 2:", shorten(data["Temp_Ext_2"]), "C") + print( + "Voltages 3V3: " + + shorten(data["MON_3V3"]) + + "V 5V1: " + + shorten(data["MON_5V1"]) + + "V 5V2: " + + shorten(data["MON_5V2"]) + + "V 7V0: " + + shorten(data["MON_7V0"]) + + "V." + ) + def close_connection(prt): cmd.close_port(prt) diff --git a/gui.py b/gui.py index 5e85a64..6bdeddf 100644 --- a/gui.py +++ b/gui.py @@ -51,13 +51,14 @@ SET_RAMP_SRAM_MODE_TEXT = 'SRAM режим' SET_RAMP_SRAM_SAMPLES_TEXT = 'SRAM точки (samples):' SET_RAMP_SRAM_AMP_TEXT = 'SRAM амплитуда (%):' SET_AD9833_SECTION_TEXT = 'Настройка пилы (AD9833)' -SET_AD9833_FREQ_TEXT = 'Частота AD9833 (Гц):' +SET_AD9833_FREQ_TEXT = 'Частота AD9833 (кГц):' SET_AD9833_MCLK_TEXT = 'MCLK AD9833 (МГц):' SET_AD9833_TRI_TEXT = 'Треугольник AD9833' SET_AD9833_BUTTON_TEXT = 'Пила AD9833' SET_DS1809_SECTION_TEXT = 'DS1809 (UC/DC)' SET_DS1809_UC_BUTTON_TEXT = 'UC импульс' SET_DS1809_DC_BUTTON_TEXT = 'DC импульс' +SET_DS1809_STATUS_TEXT = 'Позиция DS1809:' GRAPH_POINTS_NUMBER = 100 # Number of most recent data points shown on charts @@ -290,6 +291,9 @@ def setup_gui(params): [sg.Button(SET_DS1809_UC_BUTTON_TEXT, key='-DS1809UC-', disabled_button_color=("Gray22", "Blue")), sg.Button(SET_DS1809_DC_BUTTON_TEXT, key='-DS1809DC-', disabled_button_color=("Gray22", "Blue"))], + [sg.Text(SET_DS1809_STATUS_TEXT, size=(SET_TEXT_WIDTH_NEW,1)), + sg.Text(params.get('DS1809Status', '--'), key='-DS1809Status-', size=(20,1))], + [sg.HSeparator(pad=H_SEPARATOR_PAD)], [sg.Button(SET_START_BUTTON_TEXT, key='-StartCycle-', disabled_button_color=("Gray22", "Blue"), disabled=True), sg.Button(SET_STOP_BUTTON_TEXT, disabled_button_color=("Gray22", "Blue"), key='-StopCycle-', disabled=True), sg.Button(SET_RAMP_BUTTON_TEXT, key='-StartRamp-', disabled_button_color=("Gray22", "Blue")), sg.Button(SET_AD9833_BUTTON_TEXT, key='-StartRamp9833-', disabled_button_color=("Gray22", "Blue"))]]