From cb7f966081088dbe53517e6735c1bea7202e2330 Mon Sep 17 00:00:00 2001 From: Ayzen Date: Fri, 13 Mar 2026 17:50:11 +0300 Subject: [PATCH] Save current local changes --- __pycache__/device_commands.cpython-312.pyc | Bin 26739 -> 30709 bytes .../device_interaction.cpython-312.pyc | Bin 10267 -> 18423 bytes __pycache__/gui.cpython-312.pyc | Bin 24253 -> 24806 bytes _device_main.py | 13 + device_commands.py | 102 ++++++- device_interaction.py | 177 +++++++++++- gui.py | 8 + waveform.json | 259 ++++++++++++++++++ 8 files changed, 548 insertions(+), 11 deletions(-) create mode 100644 waveform.json diff --git a/__pycache__/device_commands.cpython-312.pyc b/__pycache__/device_commands.cpython-312.pyc index 05daedef29b748b6102b6cd126db318f3bcf882b..847740a3de46e956c0e5ce98d1cc94549f50e498 100644 GIT binary patch delta 7773 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 VK%WlRzi{qBqP)*_%h_az(8j4z6k*_29DTD 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 delta 2689 zcmaJ?Yiv}<6`r||ectu%+Us4vuoruAxHu4sp|%%4vVIXK=@!g3$X3vH*}0f?ch_d_ zItJI4oeC+4Qh_r`a9dT%k5(xqYEay&QhqhDnm_3e5Hy%uA`vD1;r?hX8#M`yl%AQj z1Bpb(-fzx5bLKo|&N=hh1^ml{vmbaoE(GJTKfjv!Pc=fH@Q=+QZ4m1VKwLru5u8QC ze3r5}hlepP@}yyz$ioUz&Y)qHsDL(N1GE!6po2I7ox};~a-whi9ozUMrNngx54%Yj zal@>U0MTIfkaFUIxr78s3Cv#N1tmV>1N0L=VCiYBhgP_saUEL@k}4R5OwJf-7GtR^ zjaUo^b}Zv!05hlnRB>F{m@z8Io4kU;cD2_?qfU0HrHbV}j@e#x0bh{P@(4;xM;!<~ z53)0OMw*eP@w{~2rJxy^e^1L?GA#)?Czm038oz){eBL1>-5{yV;Aw?Q)5?s@<&Qqi z<(v=sL_q76jbjJDRK!y4HD#E{M0r!avf(BKp|XqZTiD%@hP~`+X`pi&yu;`)QqXBZ zrDgCCPZnuGHK2lTGs*~}jTlX%7q>bAN6UDyy%^=a-x88B8bWzx5}lHV&?Fv0=U>{S zbz9t$e{i^aN-kMB?y$j&-^#Q99njj%=O$+oM#Sv8rkt?^IM&lJ8w00UvKKZ z(bRh*(sxVipC7$Q7Geu0&n2#D{d0ZSwf3`cC9IfWKI?A%FDqdqL!@TthZz`Leh4?Ch!}`zx0Np-Thv1Ft^w`JGUB4Pk%f z?Lft<4DPt3XPe@q` zV)$4JIeQxHOJRxA^_bHQGN-r0D(iJ$$5Sk*ZSz|w!B78$afs+l#6am#xoHjqfh`V)PF$-{l|UdlIG&Jm-K zJe-In`YvM{0FKEkF-$7}secpp@rB(0bLfs@zok@PSE?6!ZYp8sZ``UxU;)hj;W^p} zly0XQOqR3rj{`hp(ehB^C&K=*;8C+fa@w4)wybS~Uq z#ieN{hb|6Ja)@!*%kGAQRs1fj5;zfBNP-vR$z19b9R%jVMc>u|42Fm6C0zY17qjQm z*&iiOOwk8!@bj8%*qM4{tTu7+l<%hr4#FRyKm=?99s~?Sq=n@#x{@Mot*59^y71m{T^)6^+3S@NCF6?@5wWnm!MUud#n@ z`<}?U#mVnB;cBkma%S>JQkeqWl}I$b7!>=XZ!7z2OI=u;VOeq#M3TqHXfl_{Cy$*j z7$zmK`&WqlwmmZr*(H*a(p6d8uTQH zyn)!B{a4{^-QG&}<^J0b3gM&nko#-vbO-yYy}Gp)c7U7Qp5=nKH&u+(Afv#**`6la>Rt9_PJ@P6=Mt$m^1&2v=sf z(uX~DiB}6QAN-t$#lN7%H+ohe>)CwYvk}n|q>YXvo+*nu| zXk#zMUyNUD{+kk_{N8nUT#Q6NOu7<}ClWe_|B)XDet+QOsWV-=(%(;cDa7@%VnY)F zd5Q5tCT+kgm&>K{#1yU+Gl0K##SHMHSs-~gqr4qa-ZVr7i^35}E%H!Ywzv%uD&|2Z40Rpmu k_pE8`X9u3FTQ9NWruBdi7>y3Rb1m42ZR>-uze3pgAN>x1J^%m! diff --git a/__pycache__/gui.cpython-312.pyc b/__pycache__/gui.cpython-312.pyc index ddf0b4cf664588db8cbb8a26ef66ee479e5b9ca6..b7d5e24c9ad2432cd507e44f63b3fc2942275eb5 100644 GIT binary patch delta 2415 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 S*W-3d$$~!z=fsl4aK=B~=1%ee diff --git a/_device_main.py b/_device_main.py index 92f600e..88c5892 100644 --- a/_device_main.py +++ b/_device_main.py @@ -1,6 +1,7 @@ from FreeSimpleGUI import TIMEOUT_KEY, WIN_CLOSED import json import math +import os import socket import subprocess import time @@ -34,6 +35,8 @@ DS1809_INIT_STARTUP_DELAY_S = 0.35 STM32_DAC_VREF = 2.5 STM32_DAC_MAX_CODE = 4095 PA4_DAC_DEFAULT_VOLT = 0.52 +AD9102_WAVEFORM_FILE_NAME = "waveform.json" +AD9102_WAVEFORM_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), AD9102_WAVEFORM_FILE_NAME) #### ---- Functions @@ -162,6 +165,7 @@ def set_initial_params(): params['RampSramMode'] = False params['RampSramSamples'] = '' params['RampSramAmp'] = '' + params['WaveformStatus'] = f'{AD9102_WAVEFORM_FILE_NAME}: не загружен' params['Ad9833Freq'] = str(AD9833_FREQ_DEFAULT_KHZ) params['Ad9833Mclk'] = str(AD9833_MCLK_DEFAULT_MHZ) params['Ad9833Triangle'] = True @@ -573,6 +577,15 @@ if __name__ == "__main__": 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 == '-UploadWaveform-': + try: + waveform = dev.load_ad9102_waveform_file(AD9102_WAVEFORM_PATH) + dev.upload_ad9102_waveform(prt, waveform) + params['WaveformStatus'] = f'{AD9102_WAVEFORM_FILE_NAME}: {len(waveform)} точек загружено' + except Exception as exc: + print(f'AD9102 waveform upload failed: {exc}') + params['WaveformStatus'] = f'{AD9102_WAVEFORM_FILE_NAME}: ошибка - {exc}' + window['-WaveformStatus-'].update(params['WaveformStatus']) elif event == '-DS1809UC-': 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) diff --git a/device_commands.py b/device_commands.py index db9a6d3..96064ed 100644 --- a/device_commands.py +++ b/device_commands.py @@ -18,6 +18,10 @@ DS1809_CMD_TOTAL_LENGTH = 10 # Total bytes when sending DS1809 UC/DC pulse comma DS1809_CMD_HEADER = "AAAA" STM32_DAC_CMD_TOTAL_LENGTH = 10 # Total bytes when sending STM32 DAC command STM32_DAC_CMD_HEADER = "BBBB" +AD9102_WAVE_CTRL_TOTAL_LENGTH = 10 # Total bytes when sending AD9102 waveform control command +AD9102_WAVE_CTRL_HEADER = "CCCC" +AD9102_WAVE_DATA_TOTAL_LENGTH = 30 # Total bytes when sending AD9102 waveform data packet +AD9102_WAVE_DATA_HEADER = "DDDD" AD9102_SAW_STEP_DEFAULT = 1 AD9102_PAT_PERIOD_DEFAULT = 0xFFFF AD9102_PAT_PERIOD_BASE_DEFAULT = 0x02 @@ -27,6 +31,13 @@ AD9102_FLAG_SRAM_FMT = 0x0008 AD9102_SRAM_SAMPLES_DEFAULT = 16 AD9102_SRAM_HOLD_DEFAULT = 1 AD9102_SRAM_AMP_DEFAULT = 8191 +AD9102_WAVE_OPCODE_BEGIN = 0x0001 +AD9102_WAVE_OPCODE_COMMIT = 0x0002 +AD9102_WAVE_OPCODE_CANCEL = 0x0003 +AD9102_WAVE_CHUNK_SAMPLES = 12 +AD9102_SAMPLE_MIN = -8192 +AD9102_SAMPLE_MAX = 8191 +AD9102_WAVE_MAX_SAMPLES = 4096 AD9833_FLAG_ENABLE = 0x0001 AD9833_FLAG_TRIANGLE = 0x0002 AD9833_MCLK_HZ_DEFAULT = 20_000_000 @@ -219,17 +230,45 @@ def send_STM32_DAC(prt, bytestring): print("Sent: STM32 DAC command.") +def send_AD9102_waveform_control(prt, bytestring, quiet: bool = False): + ''' Control custom AD9102 SRAM upload (0xCCCC + ...). + Expected device answer: STATE. + ''' + if len(bytestring) != AD9102_WAVE_CTRL_TOTAL_LENGTH: + print("Error. Wrong parameter string for AD9102 waveform control command.") + return None + prt.write(bytestring) + if not quiet: + print("Sent: AD9102 waveform control command.") + + +def send_AD9102_waveform_data(prt, bytestring, quiet: bool = False): + ''' Send custom AD9102 SRAM samples (0xDDDD + ...). + Expected device answer: STATE. + ''' + if len(bytestring) != AD9102_WAVE_DATA_TOTAL_LENGTH: + print("Error. Wrong parameter string for AD9102 waveform data command.") + return None + prt.write(bytestring) + if not quiet: + print("Sent: AD9102 waveform data command.") + + # ---- Getting data -def get_STATE(prt): +def get_STATE(prt, quiet: bool = False): ''' Get decoded state of the device in byte format (2 bytes). ''' - print("Received "+str(prt.inWaiting())+" bytes.") + if not quiet: + print("Received "+str(prt.inWaiting())+" bytes.") if prt.inWaiting()!=2: - print("Error. Couldn't get STATE data. prt.inWaiting():", prt.inWaiting()) - print("Flushing input data:", prt.read(prt.inWaiting())) + if not quiet: + print("Error. Couldn't get STATE data. prt.inWaiting():", prt.inWaiting()) + print("Flushing input data:", prt.read(prt.inWaiting())) + else: + prt.read(prt.inWaiting()) # print("Flushing input data:", prt.read(2), prt.read(2)) return None @@ -534,6 +573,61 @@ def create_STM32_DAC_command(dac_code: int, enable: bool = True): return bytearray.fromhex(data) +def create_AD9102_waveform_control_command(opcode: int, param0: int = 0, param1: int = 0): + if opcode is None: + opcode = 0 + if param0 is None: + param0 = 0 + if param1 is None: + param1 = 0 + + opcode &= 0xFFFF + param0 &= 0xFFFF + param1 &= 0xFFFF + crc_word = opcode ^ param0 ^ param1 + + data = flipfour(AD9102_WAVE_CTRL_HEADER) # Word 0 (header) + data += flipfour(int_to_hex(opcode)) + data += flipfour(int_to_hex(param0)) + data += flipfour(int_to_hex(param1)) + data += flipfour(int_to_hex(crc_word)) + + return bytearray.fromhex(data) + + +def create_AD9102_waveform_data_command(samples_chunk): + if samples_chunk is None: + raise ValueError("AD9102 waveform chunk is missing.") + + samples = list(samples_chunk) + chunk_count = len(samples) + if chunk_count < 1 or chunk_count > AD9102_WAVE_CHUNK_SAMPLES: + raise ValueError("AD9102 waveform chunk size must be within [1, 12].") + + encoded_words = [chunk_count] + for sample in samples: + if isinstance(sample, bool) or not isinstance(sample, int): + raise ValueError("AD9102 waveform samples must be integers.") + if sample < AD9102_SAMPLE_MIN or sample > AD9102_SAMPLE_MAX: + raise ValueError("AD9102 waveform sample is out of range [-8192, 8191].") + encoded_words.append(sample & 0xFFFF) + + while len(encoded_words) < (1 + AD9102_WAVE_CHUNK_SAMPLES): + encoded_words.append(0) + + crc_word = 0 + for word in encoded_words: + crc_word ^= word + + data = flipfour(AD9102_WAVE_DATA_HEADER) # Word 0 (header) + data += flipfour(int_to_hex(encoded_words[0])) + for word in encoded_words[1:]: + data += flipfour(int_to_hex(word)) + data += flipfour(int_to_hex(crc_word)) + + return bytearray.fromhex(data) + + def encode_Input(params): if params is None: diff --git a/device_interaction.py b/device_interaction.py index ba1de18..ad2280e 100644 --- a/device_interaction.py +++ b/device_interaction.py @@ -1,4 +1,5 @@ import time +import json from datetime import datetime import device_commands as cmd @@ -7,19 +8,54 @@ import device_commands as cmd #### ---- Constants WAIT_AFTER_SEND = 0.15 # Wait after sending command before requesting input (s). +FAST_STATE_TIMEOUT_S = 0.4 +FAST_STATE_POLL_S = 0.005 #### ---- High-level port commands +def _port_sort_key(port_info): + device = str(getattr(port_info, "device", "") or "").lower() + description = str(getattr(port_info, "description", "") or "").lower() + hwid = str(getattr(port_info, "hwid", "") or "").lower() + + is_usb = ("usb" in device) or ("acm" in device) or ("usb" in description) or ("vid:pid" in hwid) + is_builtin_uart = device.startswith("/dev/ttys") + + return ( + 0 if is_usb else 1, + 1 if is_builtin_uart else 0, + device, + ) + + +def _is_preferred_serial_port(port_info): + device = str(getattr(port_info, "device", "") or "").lower() + description = str(getattr(port_info, "description", "") or "").lower() + hwid = str(getattr(port_info, "hwid", "") or "").lower() + return ("usb" in device) or ("acm" in device) or ("usb" in description) or ("vid:pid" in hwid) + + def create_port_connection(): prt = None - for port, _, _ in sorted(cmd.list_ports.comports()): + port_infos = list(cmd.list_ports.comports()) + preferred_ports = [port_info for port_info in port_infos if _is_preferred_serial_port(port_info)] + if preferred_ports: + port_infos = preferred_ports + + for port_info in sorted(port_infos, key=_port_sort_key): + port = getattr(port_info, "device", None) + if not port: + continue try: prt = cmd.setup_port_connection(port=port, baudrate=115200, timeout_sec=1) cmd.open_port(prt) - reset_port_settings(prt) - except: - prt.close() + if not reset_port_settings(prt): + raise RuntimeError(f"No valid STATE reply on {port}") + except Exception: + if prt is not None and prt.is_open: + prt.close() + prt = None continue break return prt @@ -35,14 +71,71 @@ def _print_state_reply(state_bytes): return True +def _decode_state_word(state_bytes): + if state_bytes is None: + return None + try: + return int(cmd.flipfour(state_bytes.hex()), 16) + except Exception: + return None + + +def _state_has_errors(state_bytes): + state_word = _decode_state_word(state_bytes) + if state_word is None: + return True + return (state_word & 0x00FF) != 0 + + +def _state_message(state_bytes): + if state_bytes is None: + return "STATE reply not received." + return cmd.decode_STATE(state_bytes.hex()) + + +def _wait_for_state_reply(prt, timeout_s=WAIT_AFTER_SEND, poll_s=0.01, quiet=False): + if not _wait_for_min_bytes(prt, expected_len=2, timeout_s=timeout_s, poll_s=poll_s): + if not quiet: + print("Error. Timed out waiting for STATE.") + return None + + state_bytes = cmd.get_STATE(prt, quiet=quiet) + if quiet: + return state_bytes + _print_state_reply(state_bytes) + return state_bytes + + +def _rollback_ad9102_waveform_upload(prt): + try: + cancel_cmd = cmd.create_AD9102_waveform_control_command(cmd.AD9102_WAVE_OPCODE_CANCEL) + cmd.send_AD9102_waveform_control(prt, cancel_cmd, quiet=True) + state_bytes = _wait_for_state_reply(prt, timeout_s=FAST_STATE_TIMEOUT_S, + poll_s=FAST_STATE_POLL_S, quiet=True) + if state_bytes is not None and not _state_has_errors(state_bytes): + return + except Exception: + pass + + reset_port_settings(prt) + + def reset_port_settings(prt): # Reset port settings and check status + try: + prt.reset_input_buffer() + prt.reset_output_buffer() + except Exception: + pass + cmd.send_DEFAULT_ENABLE(prt) - time.sleep(WAIT_AFTER_SEND) - status = cmd.get_STATE(prt).hex() - if status is not None: + state_bytes = _wait_for_state_reply(prt, timeout_s=max(WAIT_AFTER_SEND, 0.4), poll_s=0.01, quiet=True) + if state_bytes is not None: + status = state_bytes.hex() print("Received: STATE. State status:", cmd.decode_STATE(status), "(" + cmd.flipfour(status) + ")") print("") + return True + return False def request_state(prt): @@ -143,6 +236,76 @@ def set_stm32_dac(prt, dac_code, enable=True): return _print_state_reply(cmd.get_STATE(prt)) +def load_ad9102_waveform_file(filepath): + try: + with open(filepath, "r", encoding="utf-8") as fh: + payload = json.load(fh) + except FileNotFoundError as exc: + raise ValueError(f"Waveform file not found: {filepath}") from exc + except json.JSONDecodeError as exc: + raise ValueError(f"Invalid JSON in waveform file: {exc.msg}") from exc + + if not isinstance(payload, list): + raise ValueError("Waveform file must contain a JSON array.") + + if len(payload) < 2 or len(payload) > cmd.AD9102_WAVE_MAX_SAMPLES: + raise ValueError(f"Waveform length must be within [2, {cmd.AD9102_WAVE_MAX_SAMPLES}].") + + samples = [] + for index, sample in enumerate(payload): + if isinstance(sample, bool) or not isinstance(sample, int): + raise ValueError(f"Waveform sample #{index} is not an integer.") + if sample < cmd.AD9102_SAMPLE_MIN or sample > cmd.AD9102_SAMPLE_MAX: + raise ValueError( + f"Waveform sample #{index} is out of range " + f"[{cmd.AD9102_SAMPLE_MIN}, {cmd.AD9102_SAMPLE_MAX}]." + ) + samples.append(int(sample)) + + return samples + + +def upload_ad9102_waveform(prt, samples): + waveform = list(samples) if samples is not None else [] + if len(waveform) < 2 or len(waveform) > cmd.AD9102_WAVE_MAX_SAMPLES: + raise ValueError(f"Waveform length must be within [2, {cmd.AD9102_WAVE_MAX_SAMPLES}].") + + try: + begin_cmd = cmd.create_AD9102_waveform_control_command( + cmd.AD9102_WAVE_OPCODE_BEGIN, + param0=len(waveform), + param1=0, + ) + cmd.send_AD9102_waveform_control(prt, begin_cmd, quiet=True) + state_bytes = _wait_for_state_reply(prt, timeout_s=FAST_STATE_TIMEOUT_S, + poll_s=FAST_STATE_POLL_S, quiet=True) + if state_bytes is None or _state_has_errors(state_bytes): + raise RuntimeError(f"Waveform BEGIN failed: {_state_message(state_bytes)}") + + for offset in range(0, len(waveform), cmd.AD9102_WAVE_CHUNK_SAMPLES): + chunk = waveform[offset:offset + cmd.AD9102_WAVE_CHUNK_SAMPLES] + chunk_cmd = cmd.create_AD9102_waveform_data_command(chunk) + cmd.send_AD9102_waveform_data(prt, chunk_cmd, quiet=True) + state_bytes = _wait_for_state_reply(prt, timeout_s=FAST_STATE_TIMEOUT_S, + poll_s=FAST_STATE_POLL_S, quiet=True) + if state_bytes is None or _state_has_errors(state_bytes): + chunk_no = (offset // cmd.AD9102_WAVE_CHUNK_SAMPLES) + 1 + raise RuntimeError(f"Waveform DATA chunk {chunk_no} failed: {_state_message(state_bytes)}") + + commit_cmd = cmd.create_AD9102_waveform_control_command(cmd.AD9102_WAVE_OPCODE_COMMIT) + cmd.send_AD9102_waveform_control(prt, commit_cmd, quiet=True) + state_bytes = _wait_for_state_reply(prt, timeout_s=FAST_STATE_TIMEOUT_S, + poll_s=FAST_STATE_POLL_S, quiet=True) + if state_bytes is None or _state_has_errors(state_bytes): + raise RuntimeError(f"Waveform COMMIT failed: {_state_message(state_bytes)}") + + print(f"Uploaded AD9102 waveform ({len(waveform)} samples).") + return True + except Exception: + _rollback_ad9102_waveform_upload(prt) + raise + + def _wait_for_min_bytes(prt, expected_len, timeout_s, poll_s=0.01): deadline = time.time() + timeout_s while time.time() < deadline: diff --git a/gui.py b/gui.py index 05484b5..9ef4fae 100644 --- a/gui.py +++ b/gui.py @@ -50,6 +50,8 @@ SET_RAMP_TRI_TEXT = 'Треугольник' SET_RAMP_SRAM_MODE_TEXT = 'SRAM режим' SET_RAMP_SRAM_SAMPLES_TEXT = 'SRAM точки (samples):' SET_RAMP_SRAM_AMP_TEXT = 'SRAM амплитуда (%):' +SET_WAVEFORM_BUTTON_TEXT = 'Загрузить форму' +SET_WAVEFORM_STATUS_TEXT = 'Форма AD9102:' SET_AD9833_SECTION_TEXT = 'Настройка пилы (AD9833)' SET_AD9833_FREQ_TEXT = 'Частота AD9833 (кГц):' SET_AD9833_MCLK_TEXT = 'MCLK AD9833 (МГц):' @@ -275,6 +277,12 @@ def setup_gui(params): [sg.Text(SET_RAMP_SRAM_AMP_TEXT, size=(SET_TEXT_WIDTH_NEW,1)), sg.Input(params.get('RampSramAmp', ''), size=(SET_INPUT_WIDTH,1), key='-RampSramAmp-')], + [sg.Button(SET_WAVEFORM_BUTTON_TEXT, key='-UploadWaveform-', disabled_button_color=("Gray22", "Blue"))], + + [sg.Text(SET_WAVEFORM_STATUS_TEXT, size=(SET_TEXT_WIDTH_NEW,1)), + sg.Text(params.get('WaveformStatus', 'waveform.json: не загружен'), + key='-WaveformStatus-', size=(30,2))], + [sg.HSeparator(pad=H_SEPARATOR_PAD)], [sg.Text(SET_AD9833_SECTION_TEXT, size=(SET_TEXT_WIDTH_NEW,1))], diff --git a/waveform.json b/waveform.json new file mode 100644 index 0000000..45bdf9f --- /dev/null +++ b/waveform.json @@ -0,0 +1,259 @@ +[ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 96, + 863, + 1488, + 1920, + 2149, + 2210, + 2170, + 2119, + 2145, + 2319, + 2676, + 3211, + 3874, + 4586, + 5253, + 5787, + 6121, + 6229, + 6126, + 5867, + 5532, + 5214, + 4993, + 4921, + 5013, + 5239, + 5536, + 5818, + 5994, + 5991, + 5767, + 5320, + 4689, + 3946, + 3180, + 2478, + 1907, + 1501, + 1254, + 1122, + 1035, + 909, + 671, + 269, + 314, + 1052, + 1888, + 2740, + 3521, + 4155, + 4593, + 4821, + 4866, + 4781, + 4642, + 4523, + 4486, + 4562, + 4749, + 5012, + 5287, + 5502, + 5585, + 5487, + 5187, + 4702, + 4077, + 3383, + 2698, + 2093, + 1618, + 1293, + 1106, + 1016, + 965, + 890, + 739, + 481, + 115, + 330, + 805, + 1246, + 1591, + 1793, + 1831, + 1711, + 1470, + 1164, + 862, + 625, + 502, + 516, + 663, + 914, + 1221, + 1530, + 1794, + 1981, + 2080, + 2106, + 2094, + 2091, + 2145, + 2293, + 2554, + 2920, + 3362, + 3830, + 4264, + 4605, + 4808, + 4846, + 4719, + 4449, + 4078, + 3656, + 3235, + 2855, + 2536, + 2280, + 2069, + 1872, + 1654, + 1385, + 1046, + 640, + 188, + 271, + 691, + 1024, + 1234, + 1302, + 1231, + 1051, + 807, + 556, + 357, + 256, + 283, + 444, + 723, + 1084, + 1480, + 1863, + 2191, + 2440, + 2602, + 2690, + 2729, + 2749, + 2779, + 2835, + 2917, + 3006, + 3069, + 3065, + 2954, + 2708, + 2318, + 1801, + 1198, + 568, + 18, + 494, + 805, + 923, + 850, + 616, + 278, + 91, + 416, + 633, + 698, + 594, + 331, + 55, + 516, + 997, + 1452, + 1849, + 2176, + 2439, + 2659, + 2858, + 3056, + 3260, + 3457, + 3619, + 3705, + 3674, + 3492, + 3148, + 2658, + 2068, + 1451, + 895, + 485, + 292, + 354, + 665, + 1174, + 1792, + 2402, + 2881, + 3119, + 3039, + 2612, + 1864, + 868, + 264, + 1402, + 2416, + 3204, + 3702 +]