From fbcbdfbff0be6b1ed6fe8d632c430fef00b90efd Mon Sep 17 00:00:00 2001 From: Stefal Date: Fri, 27 Oct 2023 13:09:42 +0000 Subject: [PATCH 001/127] More controls on modem status --- tools/modem_config.py | 5 ++- tools/switch_to_public_ip.py | 65 +++++++++++++++++++++++++++++++----- unit/modem_public_ip.service | 4 +-- 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/tools/modem_config.py b/tools/modem_config.py index c695b38b..d292cf44 100755 --- a/tools/modem_config.py +++ b/tools/modem_config.py @@ -67,7 +67,10 @@ def arg_parse(): print("Data network registration", modem.get_eps_network_registration_status()) print("Network mode selection: ", modem.get_network_mode()) print("Current network mode: ", modem.get_current_network_mode().name) - print("Current network name/operator: ", modem.get_network_operator()) + try: + print("Current network name/operator: ", modem.get_network_operator()) + except: + print("Current network name/operator: NO SERVICE") print("EU system information: ", modem.get_eu_system_informations()) data_mode = modem.get_data_connection_mode() if data_mode.name == 'ECM': diff --git a/tools/switch_to_public_ip.py b/tools/switch_to_public_ip.py index c857045d..1f8ca6d1 100644 --- a/tools/switch_to_public_ip.py +++ b/tools/switch_to_public_ip.py @@ -2,7 +2,9 @@ import os import sys +import time import nmcli + if os.getenv("SUDO_USER") is not None: homedir = os.path.join("/home", os.getenv("SUDO_USER")) else: @@ -13,8 +15,9 @@ nmcli.disable_use_sudo() CONN_NAME='Cellular Modem' +MODEM_PORT='/dev/ttymodemAT' -def sleep(timeout, retry=50): +def sleep(timeout, retry=10): def the_real_decorator(function): def wrapper(*args, **kwargs): retries = 0 @@ -24,7 +27,7 @@ def wrapper(*args, **kwargs): if value is not None: return value except: - print(f'Sleeping for {timeout + retries*timeout} seconds') + print(f'{function.__name__}: Sleeping for {timeout + retries*timeout} seconds') time.sleep(timeout + retries*timeout) retries += 1 @@ -36,27 +39,71 @@ def check_modem(): nmcli.connection.show(CONN_NAME) if nmcli.connection.show(CONN_NAME).get("GENERAL.STATE") == 'activated': return True - + +@sleep(10, retry=2) +def check_network_registration(): + try: + modem = Modem(MODEM_PORT) + network_reg = int(modem.get_network_registration_status().split(",")[1]) + if network_reg == 1 or network_reg == 2 or network_reg == 5: + return True + else: + return False + except Exception as e: + print(e) + raise Exception + finally: + modem.close() + @sleep(10) def get_public_ip_address(): - return modem.get_ip_address() + try: + modem = Modem(MODEM_PORT) + public_ip = modem.get_ip_address() + + except Exception as e: + print (e) + raise Exception + finally: + print("closing modem connexion") + modem.close() + return public_ip @sleep(10) def get_in_use_ip_address(): return nmcli.connection.show(CONN_NAME)['IP4.ADDRESS[1]'].split('/')[0] check_modem() -modem = Modem('/dev/ttymodemAT') +network_reg = check_network_registration() ip_in_use = get_in_use_ip_address() public_ip = get_public_ip_address() print("Ip address in use: ", ip_in_use) print("Public Ip address: ", public_ip) -if ip_in_use != public_ip: - modem.set_usbnetip_mode(1) - print("Request to switch to public IP address done!") - print("It could take a few minutes to be active") +if ip_in_use == None or public_ip == None or network_reg == False: + print("Modem problem. Switching to airplane mode and back to normal") + try: + modem = Modem(MODEM_PORT) + modem.custom_read_lines('AT+CFUN=0') + time.sleep(20) + modem.custom_read_lines('AT+CFUN=1') + except Exception as e: + print(e) + finally: + modem.close() + +elif ip_in_use != public_ip: + try: + modem = Modem(MODEM_PORT) + modem.set_usbnetip_mode(1) + print("Request to switch to public IP address done!") + print("It could take a few minutes to be active") + except Exception as e: + print(e) + finally: + print("closing modem connexion") + modem.close() else: print("We are already using the public Ip") diff --git a/unit/modem_public_ip.service b/unit/modem_public_ip.service index 946d3d53..b5f037d7 100644 --- a/unit/modem_public_ip.service +++ b/unit/modem_public_ip.service @@ -1,5 +1,5 @@ [Unit] -Description=Switch Simcom modem to public ip address +Description=Check Simcom modem ip address After=network-online.target Wants=network-online.target @@ -7,8 +7,6 @@ Wants=network-online.target Type=forking User={user} ExecStart={python_path} {script_path}/tools/switch_to_public_ip.py -Restart=on-failure -RestartSec=30 [Install] WantedBy=multi-user.target \ No newline at end of file From d42c89c67c7cd148b2719ad20e212f8462ad58f2 Mon Sep 17 00:00:00 2001 From: Stefal Date: Sun, 29 Oct 2023 13:03:27 +0000 Subject: [PATCH 002/127] change service type. forking doesn't work for a non daemonized script --- unit/modem_public_ip.service | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/modem_public_ip.service b/unit/modem_public_ip.service index b5f037d7..0e82ef9a 100644 --- a/unit/modem_public_ip.service +++ b/unit/modem_public_ip.service @@ -4,9 +4,9 @@ After=network-online.target Wants=network-online.target [Service] -Type=forking +Type=simple User={user} ExecStart={python_path} {script_path}/tools/switch_to_public_ip.py [Install] -WantedBy=multi-user.target \ No newline at end of file +WantedBy=multi-user.target From 9416e849e1c77cb4b8b48958469543ffb309b46f Mon Sep 17 00:00:00 2001 From: Stefal Date: Wed, 8 Nov 2023 21:16:08 +0100 Subject: [PATCH 003/127] more print() to help me debugging --- tools/switch_to_public_ip.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/switch_to_public_ip.py b/tools/switch_to_public_ip.py index 1f8ca6d1..bfac5b60 100644 --- a/tools/switch_to_public_ip.py +++ b/tools/switch_to_public_ip.py @@ -78,15 +78,18 @@ def get_in_use_ip_address(): ip_in_use = get_in_use_ip_address() public_ip = get_public_ip_address() -print("Ip address in use: ", ip_in_use) -print("Public Ip address: ", public_ip) +print("Internal Ip address in use: ", ip_in_use) +print("Modem public Ip address: ", public_ip) if ip_in_use == None or public_ip == None or network_reg == False: print("Modem problem. Switching to airplane mode and back to normal") try: + print("Connecting to modem...") modem = Modem(MODEM_PORT) + print("Sending AT+CFUN=0") modem.custom_read_lines('AT+CFUN=0') time.sleep(20) + print("Sending AT+CFUN=1") modem.custom_read_lines('AT+CFUN=1') except Exception as e: print(e) From 8386646c77c6c1e7c8d8c9484c2fcc5491b5758f Mon Sep 17 00:00:00 2001 From: Stefal Date: Wed, 8 Nov 2023 21:16:57 +0100 Subject: [PATCH 004/127] pin werkzeug version --- web_app/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/web_app/requirements.txt b/web_app/requirements.txt index 28e616cf..1ee56a51 100644 --- a/web_app/requirements.txt +++ b/web_app/requirements.txt @@ -1,4 +1,5 @@ cryptography==38.0.0 +Werkzeug=2.2.2 itsdangerous==2.1.2 Flask==2.2.2 Flask-SocketIO==5.3.2 From ab32fa55698078cf063cfe67e3746998cd5d9cb8 Mon Sep 17 00:00:00 2001 From: Stefal Date: Tue, 14 Nov 2023 17:18:09 +0100 Subject: [PATCH 005/127] add nmcli requirement --- web_app/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/web_app/requirements.txt b/web_app/requirements.txt index 1ee56a51..d84cdfa4 100644 --- a/web_app/requirements.txt +++ b/web_app/requirements.txt @@ -8,6 +8,7 @@ greenlet==1.1.3 Bootstrap-Flask==2.2.0 Flask-WTF==1.1.1 Flask-Login==0.6.2 +nmcli==1.3.0 #On some linux distribution, pexpect and psutil are already installed #and can't be upgraded with pip. In these cases, pip fail, leaving #some package uninstalled. From 90696ad61bfb3e360ee59f67beeb7b82c8839d28 Mon Sep 17 00:00:00 2001 From: Stefal Date: Wed, 7 Feb 2024 15:07:25 +0100 Subject: [PATCH 006/127] add heading --- web_app/templates/diagnostic.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_app/templates/diagnostic.html b/web_app/templates/diagnostic.html index afd810ec..6c705f9a 100644 --- a/web_app/templates/diagnostic.html +++ b/web_app/templates/diagnostic.html @@ -15,8 +15,8 @@

- {{ service.sysctl_status|safe }}
- {{ service.journalctl|safe }}
+

STATUS:

{{ service.sysctl_status|safe }}

+

JOURNAL:

{{ service.journalctl|safe }}

{% endfor %} From 15ea5a40abb3cbdcd7a9a801be798b04361692d6 Mon Sep 17 00:00:00 2001 From: Stefal Date: Thu, 8 Feb 2024 10:32:19 +0100 Subject: [PATCH 007/127] compress sbf files fix #366 --- archive_and_clean.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archive_and_clean.sh b/archive_and_clean.sh index 5d85a2e2..3f5f094a 100755 --- a/archive_and_clean.sh +++ b/archive_and_clean.sh @@ -30,7 +30,7 @@ done #archive and compress previous day's gnss data. #find . -maxdepth 1 -type f -mtime -1 -mmin +60 -name "*.ubx*" -exec tar -jcvf ${archive_name} --remove-files {} +; -find . -maxdepth 1 -type f -mtime -960 -mmin +60 \( -name "*.rtcm*" -o -name "*.nov*" -o -name "*.oem*" -o -name "*.ubx*" -o -name "*.ss2*" -o -name "*.hemis*" -o -name "*.stq*" -o -name "*.javad*" -o -name "*.nvs*" -o -name "*.binex*" \) -exec zip -m9 ${archive_name} {} +; +find . -maxdepth 1 -type f -mtime -960 -mmin +60 \( -name "*.rtcm*" -o -name "*.nov*" -o -name "*.oem*" -o -name "*.ubx*" -o -name "*.ss2*" -o -name "*.hemis*" -o -name "*.stq*" -o -name "*.javad*" -o -name "*.nvs*" -o -name "*.binex*" -o -name "*.sbf*" \) -exec zip -m9 ${archive_name} {} +; #delete gnss data older than x days. #find . -maxdepth 1 -type f -name "*.tar.bz2" -mtime +${archive_rotate} -delete From a335a970ddfe49f5f3f529b247e08c7ad48524fd Mon Sep 17 00:00:00 2001 From: Stefal Date: Thu, 15 Feb 2024 21:50:55 +0100 Subject: [PATCH 008/127] update armbian-ramlog fix https://github.com/Stefal/build/issues/16 --- rtkbase_update.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/rtkbase_update.sh b/rtkbase_update.sh index b46708b4..b6bbc12d 100755 --- a/rtkbase_update.sh +++ b/rtkbase_update.sh @@ -337,6 +337,19 @@ upd_2.4.2() { return 0 } +upd_2.5.0 () { + # only for Orange Pi Zero, update armbian-ramlog (https://github.com/Stefal/build/issues/16) + computer_model=$(tr -d '\0' < /sys/firmware/devicetree/base/model) + sbc_array=('Xunlong Orange Pi Zero') + if printf '%s\0' "${sbc_array[@]}" | grep -Fxqz -- "${computer_model}" && + lsb_release -c | grep -qE 'bullseye|bookworm' && + grep -qE 'armbian' /etc/os-release + then + echo 'Updating armbian-ramlog' + sed -i 's/armbian-ramlog)" | while/armbian-ramlog)|\\.journal" | while/' /usr/lib/armbian/armbian-ramlog + fi + # end of Orange Pi Zero section +} #check if we can apply the update #FOR THE OLDER ME -> Don't forget to modify the os detection if there is a 2.5.x release !!! [[ $checking == '--checking' ]] && check_before_update && exit From 3f3e59924d8454cb0e854f5f619a6ec52067064e Mon Sep 17 00:00:00 2001 From: Stefal Date: Thu, 29 Feb 2024 21:49:48 +0100 Subject: [PATCH 009/127] fix https://github.com/rtklibexplorer/RTKLIB/issues/186 --- tools/convbin.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/convbin.sh b/tools/convbin.sh index ddae8d3f..4732b4d2 100755 --- a/tools/convbin.sh +++ b/tools/convbin.sh @@ -33,7 +33,7 @@ convert_to_rinex_ign() { -hp "${ANT_POSITION}" -ha 0000/"${ANT_TYPE}" \ -hr 0000/"${RECEIVER}"/"${REC_VERSION}" \ -f 2 -y R -y E -y J -y S -y C -y I \ - -od -os -oi -ot -ti 30 -tt 0 \ + -od -os -oi -ot -ti 30 -tt 0.005 \ -ro "${REC_OPTION}" -o "${RINEX_FILE}" return $? } From d71d9283ff2fd8df43397bc4ece4c0f6440f5f60 Mon Sep 17 00:00:00 2001 From: Stefal Date: Fri, 22 Mar 2024 18:36:23 +0100 Subject: [PATCH 010/127] add ping host test --- tools/switch_to_public_ip.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/switch_to_public_ip.py b/tools/switch_to_public_ip.py index bfac5b60..6d9ca809 100644 --- a/tools/switch_to_public_ip.py +++ b/tools/switch_to_public_ip.py @@ -73,15 +73,22 @@ def get_public_ip_address(): def get_in_use_ip_address(): return nmcli.connection.show(CONN_NAME)['IP4.ADDRESS[1]'].split('/')[0] +@sleep(10) +def ping(host): + res = os.system("ping -c 4 " + host + ' >/dev/null') + return res == 0 + check_modem() network_reg = check_network_registration() ip_in_use = get_in_use_ip_address() public_ip = get_public_ip_address() - +ping_host = ping('caster.centipede.fr') or ping('pch.net') print("Internal Ip address in use: ", ip_in_use) print("Modem public Ip address: ", public_ip) +print("Ping caster.centipede.fr or pch.net", ping_host) + -if ip_in_use == None or public_ip == None or network_reg == False: +if ip_in_use == None or public_ip == None or network_reg == False or ping_host == False: print("Modem problem. Switching to airplane mode and back to normal") try: print("Connecting to modem...") From be0970d6905a7b79ef4305c5ee81646a65512204 Mon Sep 17 00:00:00 2001 From: Stefal Date: Sat, 23 Mar 2024 20:48:11 +0100 Subject: [PATCH 011/127] add tcp client, udp server and udp client service --- run_cast.sh | 22 ++- settings.conf.default | 38 ++++- tools/install.sh | 3 + tools/uninstall.sh | 3 + unit/disabled/str2str_rtcm_udp_client.service | 21 +++ unit/disabled/str2str_rtcm_udp_svr.service | 21 +++ unit/str2str_rtcm_client.service | 21 +++ web_app/RTKBaseConfigManager.py | 36 ++++- web_app/server.py | 23 ++- web_app/static/settings.js | 110 +++++++++++-- web_app/templates/settings.html | 147 +++++++++++++++++- 11 files changed, 423 insertions(+), 22 deletions(-) create mode 100644 unit/disabled/str2str_rtcm_udp_client.service create mode 100644 unit/disabled/str2str_rtcm_udp_svr.service create mode 100644 unit/str2str_rtcm_client.service diff --git a/run_cast.sh b/run_cast.sh index afdfe2c7..b0d0a9d6 100755 --- a/run_cast.sh +++ b/run_cast.sh @@ -38,7 +38,15 @@ out_file="file://${datadir}/${file_name}.${receiver_format}::T::S=${file_rotate_ out_rtcm_svr="tcpsvr://:${rtcm_svr_port}#rtcm3 -msg ${rtcm_svr_msg} -p ${position}" #add receiver options if it exists -[[ ! -z "${rtcm_receiver_options}" ]] && out_rtcm_svr=""${out_rtcm_svr}" -opt "${rtcm_receiver_options}"" +[[ ! -z "${rtcm_svr_receiver_options}" ]] && out_rtcm_svr=""${out_rtcm_svr}" -opt "${rtcm_svr_receiver_options}"" + +out_rtcm_client="tcpcli://${rtcm_client_addr}:${rtcm_client_port}#rtcm3 -msg ${rtcm_client_msg} -p ${position}" +#add receiver options if it exists +[[ ! -z "${rtcm_client_receiver_options}" ]] && out_rtcm_client=""${out_rtcm_client}" -opt "${rtcm_receiver_client_options}"" + +out_rtcm_udp_svr="udpsvr://:${rtcm_udp_svr_port}#rtcm3 -msg ${rtcm_udp_svr_msg} -p ${position}" +#add receiver options if it exists +[[ ! -z "${rtcm_udp_svr_receiver_options}" ]] && out_rtcm_udp_svr=""${out_rtcm_udp_svr}" -opt "${rtcm_udp_svr_receiver_options}"" out_rtcm_serial="serial://${out_com_port}:${out_com_port_settings}#rtcm3 -msg ${rtcm_serial_msg} -p ${position}" #add receiver options if it exists @@ -71,7 +79,17 @@ mkdir -p ${logdir} #echo ${cast} -in ${!1} -out $out_rtcm_svr ${cast} -in ${!1} -out ${out_rtcm_svr} -i "${receiver_info}" -a "${antenna_info}" -t ${level} -fl ${logdir}/str2str_rtcm_svr.log & ;; - + + out_rtcm_client) + #echo ${cast} -in ${!1} -out $out_rtcm_client + ${cast} -in ${!1} -out ${out_rtcm_client} -i "${receiver_info}" -a "${antenna_info}" -t ${level} -fl ${logdir}/str2str_rtcm_client.log & + ;; + + out_rtcm_udp_svr) + #echo ${cast} -in ${!1} -out $out_rtcm_udp_svr + ${cast} -in ${!1} -out ${out_rtcm_udp_svr} -i "${receiver_info}" -a "${antenna_info}" -t ${level} -fl ${logdir}/str2str_rtcm_udp_svr.log & + ;; + out_rtcm_serial) #echo ${cast} -in ${!1} -out $out_rtcm_serial ${cast} -in ${!1} -out ${out_rtcm_serial} -i "${receiver_info}" -a "${antenna_info}" -t ${level} -fl ${logdir}/str2str_rtcm_serial.log & diff --git a/settings.conf.default b/settings.conf.default index cfe9f01c..16e3dbea 100644 --- a/settings.conf.default +++ b/settings.conf.default @@ -127,7 +127,43 @@ rtcm_svr_port='5016' #messages for rtcm local use rtcm_svr_msg='1004,1005(10),1006,1008(10),1012,1019,1020,1033(10),1042,1045,1046,1077,1087,1097,1107,1127,1230' #Receiver dependent options -rtcm_receiver_options='' +rtcm_svr_receiver_options='' + +[rtcm_client] + +# RTCM client options + +#port for rtcm client +rtcm_client_addr='' +rtcm_client_port='80' +#messages for rtcm client use +rtcm_client_msg='1004,1005(10),1006,1008(10),1012,1019,1020,1033(10),1042,1045,1046,1077,1087,1097,1107,1127,1230' +#Receiver dependent options +rtcm_client_receiver_options='' + +[rtcm_udp_svr] + +#RTCM UDP Server options + +#port for rtcm UDP use +rtcm_udp_svr_port='' +#messages for rtcm udp use +rtcm_udp_svr_msg='1004,1005(10),1006,1008(10),1012,1019,1020,1033(10),1042,1045,1046,1077,1087,1097,1107,1127,1230' +#Receiver dependent options +rtcm_udp_svr_receiver_options='' + +[rtcm_udp_client] + +#RTCM UDP Client options + +#address for rtcm UDP +rtcm_udp_client_addr='' +#port for rtcm UDP use +rtcm_udp_client_port='' +#messages for rtcm udp use +rtcm_udp_client_msg='1004,1005(10),1006,1008(10),1012,1019,1020,1033(10),1042,1045,1046,1077,1087,1097,1107,1127,1230' +#Receiver dependent options +rtcm_udp_client_receiver_options='' [rtcm_serial] diff --git a/tools/install.sh b/tools/install.sh index 8c0f2c44..bbff9ebb 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -512,6 +512,9 @@ configure_gnss(){ sudo -u "${RTKBASE_USER}" sed -i s/^ntrip_b_receiver_options=.*/ntrip_b_receiver_options=\'-TADJ=1\'/ "${rtkbase_path}"/settings.conf && \ sudo -u "${RTKBASE_USER}" sed -i s/^local_ntripc_receiver_options=.*/local_ntripc_receiver_options=\'-TADJ=1\'/ "${rtkbase_path}"/settings.conf && \ sudo -u "${RTKBASE_USER}" sed -i s/^rtcm_receiver_options=.*/rtcm_receiver_options=\'-TADJ=1\'/ "${rtkbase_path}"/settings.conf && \ + sudo -u "${RTKBASE_USER}" sed -i s/^rtcm_client_receiver_options=.*/rtcm_client_receiver_options=\'-TADJ=1\'/ "${rtkbase_path}"/settings.conf && \ + sudo -u "${RTKBASE_USER}" sed -i s/^rtcm_udp_svr_receiver_options=.*/rtcm_udp_svr_receiver_options=\'-TADJ=1\'/ "${rtkbase_path}"/settings.conf && \ + sudo -u "${RTKBASE_USER}" sed -i s/^rtcm_udp_client_receiver_options=.*/rtcm_udp_client_receiver_options=\'-TADJ=1\'/ "${rtkbase_path}"/settings.conf && \ sudo -u "${RTKBASE_USER}" sed -i s/^rtcm_serial_receiver_options=.*/rtcm_serial_receiver_options=\'-TADJ=1\'/ "${rtkbase_path}"/settings.conf && \ return $? diff --git a/tools/uninstall.sh b/tools/uninstall.sh index dde38774..5a8f8ad1 100755 --- a/tools/uninstall.sh +++ b/tools/uninstall.sh @@ -8,6 +8,9 @@ for service_name in str2str_tcp.service \ str2str_ntrip_B.service \ str2str_local_ntrip_caster \ str2str_rtcm_svr.service \ + str2str_rtcm_client.service \ + str2str_rtcm_udp_svr.service \ + str2str_rtcm_udp_client.service \ str2str_rtcm_serial.service \ str2str_file.service \ rtkbase_web \ diff --git a/unit/disabled/str2str_rtcm_udp_client.service b/unit/disabled/str2str_rtcm_udp_client.service new file mode 100644 index 00000000..d4a0c4c2 --- /dev/null +++ b/unit/disabled/str2str_rtcm_udp_client.service @@ -0,0 +1,21 @@ +[Unit] +Description=RTKBase RTCM UDP Client +#After=network-online.target +#Wants=network-online.target +Requires=str2str_tcp.service + +[Service] +Type=forking +User={user} +ExecStart={script_path}/run_cast.sh in_tcp out_rtcm_udp_client +Restart=on-failure +RestartSec=30 +#Limiting log to 1 msg per minute +LogRateLimitIntervalSec=1 minute +LogRateLimitBurst=1 +ProtectHome=read-only +ProtectSystem=strict +ReadWritePaths={script_path} + +[Install] +WantedBy=multi-user.target diff --git a/unit/disabled/str2str_rtcm_udp_svr.service b/unit/disabled/str2str_rtcm_udp_svr.service new file mode 100644 index 00000000..e7ce44d8 --- /dev/null +++ b/unit/disabled/str2str_rtcm_udp_svr.service @@ -0,0 +1,21 @@ +[Unit] +Description=RTKBase RTCM UDP Server +#After=network-online.target +#Wants=network-online.target +Requires=str2str_tcp.service + +[Service] +Type=forking +User={user} +ExecStart={script_path}/run_cast.sh in_tcp out_rtcm_udp_svr +Restart=on-failure +RestartSec=30 +#Limiting log to 1 msg per minute +LogRateLimitIntervalSec=1 minute +LogRateLimitBurst=1 +ProtectHome=read-only +ProtectSystem=strict +ReadWritePaths={script_path} + +[Install] +WantedBy=multi-user.target diff --git a/unit/str2str_rtcm_client.service b/unit/str2str_rtcm_client.service new file mode 100644 index 00000000..f7101fc6 --- /dev/null +++ b/unit/str2str_rtcm_client.service @@ -0,0 +1,21 @@ +[Unit] +Description=RTKBase RTCM Client +#After=network-online.target +#Wants=network-online.target +Requires=str2str_tcp.service + +[Service] +Type=forking +User={user} +ExecStart={script_path}/run_cast.sh in_tcp out_rtcm_client +Restart=on-failure +RestartSec=30 +#Limiting log to 1 msg per minute +LogRateLimitIntervalSec=1 minute +LogRateLimitBurst=1 +ProtectHome=read-only +ProtectSystem=strict +ReadWritePaths={script_path} + +[Install] +WantedBy=multi-user.target diff --git a/web_app/RTKBaseConfigManager.py b/web_app/RTKBaseConfigManager.py index f365c973..91de2da0 100644 --- a/web_app/RTKBaseConfigManager.py +++ b/web_app/RTKBaseConfigManager.py @@ -161,10 +161,41 @@ def get_rtcm_svr_settings(self): and remove the single quotes. """ ordered_rtcm_svr = [{"source_section" : "rtcm_svr"}] - for key in ("rtcm_svr_port", "rtcm_svr_msg", "rtcm_receiver_options"): + for key in ("rtcm_svr_port", "rtcm_svr_msg", "rtcm_svr_receiver_options"): ordered_rtcm_svr.append({key : self.config.get('rtcm_svr', key).strip("'")}) return ordered_rtcm_svr + def get_rtcm_client_settings(self): + """ + Get a subset of the settings from the file section in an ordered object + and remove the single quotes. + """ + ordered_rtcm_client = [{"source_section" : "rtcm_client"}] + for key in ("rtcm_client_addr", "rtcm_client_port", "rtcm_client_msg", "rtcm_client_receiver_options"): + ordered_rtcm_client.append({key : self.config.get('rtcm_client', key).strip("'")}) + print("ORDERED RTCM CLIENT", ordered_rtcm_client) + return ordered_rtcm_client + + def get_rtcm_udp_svr_settings(self): + """ + Get a subset of the settings from the file section in an ordered object + and remove the single quotes. + """ + ordered_rtcm_udp_svr = [{"source_section" : "rtcm_udp_svr"}] + for key in ("rtcm_udp_svr_port", "rtcm_udp_svr_msg", "rtcm_udp_svr_receiver_options"): + ordered_rtcm_udp_svr.append({key : self.config.get('rtcm_udp_svr', key).strip("'")}) + return ordered_rtcm_udp_svr + + def get_rtcm_udp_client_settings(self): + """ + Get a subset of the settings from the file section in an ordered object + and remove the single quotes. + """ + ordered_rtcm_udp_client = [{"source_section" : "rtcm_udp_client"}] + for key in ("rtcm_udp_client_addr", "rtcm_udp_client_port", "rtcm_udp_client_msg", "rtcm_udp_client_receiver_options"): + ordered_rtcm_udp_client.append({key : self.config.get('rtcm_udp_client', key).strip("'")}) + return ordered_rtcm_udp_client + def get_rtcm_serial_settings(self): """ Get a subset of the settings from the file section in an ordered object @@ -187,6 +218,9 @@ def get_ordered_settings(self): ordered_settings['local_ntripc'] = self.get_local_ntripc_settings() ordered_settings['file'] = self.get_file_settings() ordered_settings['rtcm_svr'] = self.get_rtcm_svr_settings() + ordered_settings['rtcm_client'] = self.get_rtcm_client_settings() + ordered_settings['rtcm_udp_svr'] = self.get_rtcm_udp_svr_settings() + ordered_settings['rtcm_udp_client'] = self.get_rtcm_udp_client_settings() ordered_settings['rtcm_serial'] = self.get_rtcm_serial_settings() return ordered_settings diff --git a/web_app/server.py b/web_app/server.py index 0b82d294..41008faf 100755 --- a/web_app/server.py +++ b/web_app/server.py @@ -105,6 +105,9 @@ {"service_unit" : "str2str_ntrip_B.service", "name" : "ntrip_B"}, {"service_unit" : "str2str_local_ntrip_caster.service", "name" : "local_ntrip_caster"}, {"service_unit" : "str2str_rtcm_svr.service", "name" : "rtcm_svr"}, + {"service_unit" : "str2str_rtcm_client.service", "name" : "rtcm_client"}, + {"service_unit" : "str2str_rtcm_udp_svr.service", "name" : "rtcm_udp_svr"}, + {'service_unit' : 'str2str_rtcm_udp_client.service', "name" : "rtcm_udp_client"}, {'service_unit' : 'str2str_rtcm_serial.service', "name" : "rtcm_serial"}, {"service_unit" : "str2str_file.service", "name" : "file"}, {'service_unit' : 'rtkbase_archive.timer', "name" : "archive_timer"}, @@ -400,17 +403,23 @@ def settings_page(): ntrip_A_settings = rtkbaseconfig.get_ntrip_A_settings() ntrip_B_settings = rtkbaseconfig.get_ntrip_B_settings() local_ntripc_settings = rtkbaseconfig.get_local_ntripc_settings() - file_settings = rtkbaseconfig.get_file_settings() rtcm_svr_settings = rtkbaseconfig.get_rtcm_svr_settings() + rtcm_client_settings = rtkbaseconfig.get_rtcm_client_settings() + rtcm_udp_svr_settings = rtkbaseconfig.get_rtcm_udp_svr_settings() + rtcm_udp_client_settings = rtkbaseconfig.get_rtcm_udp_client_settings() rtcm_serial_settings = rtkbaseconfig.get_rtcm_serial_settings() + file_settings = rtkbaseconfig.get_file_settings() return render_template("settings.html", main_settings = main_settings, ntrip_A_settings = ntrip_A_settings, ntrip_B_settings = ntrip_B_settings, local_ntripc_settings = local_ntripc_settings, - file_settings = file_settings, rtcm_svr_settings = rtcm_svr_settings, + rtcm_client_settings = rtcm_client_settings, + rtcm_udp_svr_settings = rtcm_udp_svr_settings, + rtcm_udp_client_settings = rtcm_udp_client_settings, rtcm_serial_settings = rtcm_serial_settings, + file_settings = file_settings, os_infos = distro.info(),) @app.route('/logs') @@ -892,7 +901,7 @@ def update_settings(json_msg): #Restart service if needed if source_section == "main": - restartServices(("main", "ntrip_A", "ntrip_B", "local_ntrip_caster", "rtcm_svr", "file", "rtcm_serial")) + restartServices(("main", "ntrip_A", "ntrip_B", "local_ntrip_caster", "rtcm_svr", "rtcm_client", "rtcm_udp_svr", "rtcm_udp_client", "file", "rtcm_serial")) elif source_section == "ntrip_A": restartServices(("ntrip_A",)) elif source_section == "ntrip_B": @@ -901,6 +910,12 @@ def update_settings(json_msg): restartServices(("local_ntrip_caster",)) elif source_section == "rtcm_svr": restartServices(("rtcm_svr",)) + elif source_section == "rtcm_client": + restartServices(("rtcm_client",)) + elif source_section == "rtcm_udp_svr": + restartServices(("rtcm_udp_svr",)) + elif source_section == "rtcm_udp_client": + restartServices(("rtcm_udp_client",)) elif source_section == "rtcm_serial": restartServices(("rtcm_serial",)) elif source_section == "local_storage": @@ -922,7 +937,7 @@ def update_settings(json_msg): #check if we run RTKBase for the first time after an update #and restart some services to let them send the new release number. if rtkbaseconfig.get("general", "updated", fallback="False").lower() == "true": - restartServices(["ntrip_A", "ntrip_B", "local_ntrip_caster", "rtcm_svr", "rtcm_serial"]) + restartServices(["ntrip_A", "ntrip_B", "local_ntrip_caster", "rtcm_svr", "rtcm_client", "rtcm_udp_svr", "rtcm_udp_client", "rtcm_serial"]) rtkbaseconfig.remove_option("general", "updated") rtkbaseconfig.write_file() #Start a "manager" thread diff --git a/web_app/static/settings.js b/web_app/static/settings.js index dc59f1c4..c33b7e9e 100644 --- a/web_app/static/settings.js +++ b/web_app/static/settings.js @@ -228,7 +228,7 @@ $(document).ready(function () { socket.emit("services switch", {"name" : "local_ntrip_caster", "active" : switchStatus}); }) - // #################### RTCM server service Switch ######################### + // #################### RTCM TCP server service Switch ######################### var rtcmSvrSwitch = $('#rtcm_svr-switch'); // set the switch to on/off depending of the service status @@ -257,11 +257,99 @@ $(document).ready(function () { socket.emit("services switch", {"name" : "rtcm_svr", "active" : switchStatus}); }) + // #################### RTCM TCP client service Switch ######################### + + var rtcmClientSwitch = $('#rtcm_client-switch'); + // set the switch to on/off depending of the service status + if (servicesStatus[5].active === true) { + //document.querySelector("#main-switch").bootstrapToggle('on'); + rtcmClientSwitch.bootstrapToggle('on', true); + } else { + //document.querySelector("#main-switch").bootstrapToggle('off'); + rtcmClientSwitch.bootstrapToggle('off', true); + } + //console.log(servicesStatus[3]); + if (servicesStatus[5].btn_color) { + rtcmClientSwitch.bootstrapToggle('setOnStyle', servicesStatus[5].btn_color); + } + if (servicesStatus[5].btn_off_color) { + rtcmClientSwitch.bootstrapToggle('setOffStyle', servicesStatus[5].btn_off_color); + } + + // event for switching on/off service on user mouse click + //TODO When the switch changes its position, this event seems attached before + //the switch finish its transition, then fire another event. + $( "#rtcm_client-switch" ).one("change", function(e) { + var switchStatus = $(this).prop('checked'); + //console.log(" e : " + e); + //console.log("RTCM Client SwitchStatus : " + switchStatus); + socket.emit("services switch", {"name" : "rtcm_client", "active" : switchStatus}); + }) + + // #################### RTCM UDP server service Switch ######################### + + var rtcmUdpSvrSwitch = $('#rtcm_udp_svr-switch'); + // set the switch to on/off depending of the service status + if (servicesStatus[6].active === true) { + //document.querySelector("#main-switch").bootstrapToggle('on'); + rtcmUdpSvrSwitch.bootstrapToggle('on', true); + } else { + //document.querySelector("#main-switch").bootstrapToggle('off'); + rtcmUdpSvrSwitch.bootstrapToggle('off', true); + } + //console.log(servicesStatus[3]); + if (servicesStatus[6].btn_color) { + rtcmUdpSvrSwitch.bootstrapToggle('setOnStyle', servicesStatus[6].btn_color); + } + if (servicesStatus[6].btn_off_color) { + rtcmUdpSvrSwitch.bootstrapToggle('setOffStyle', servicesStatus[6].btn_off_color); + } + + // event for switching on/off service on user mouse click + //TODO When the switch changes its position, this event seems attached before + //the switch finish its transition, then fire another event. + $( "#rtcm_udp_svr-switch" ).one("change", function(e) { + var switchStatus = $(this).prop('checked'); + //console.log(" e : " + e); + //console.log("RTCM UDP Server SwitchStatus : " + switchStatus); + socket.emit("services switch", {"name" : "rtcm_udp_svr", "active" : switchStatus}); + }) + + // #################### RTCM UDP client service Switch ######################### + + var rtcmUdpClientSwitch = $('#rtcm_udp_client-switch'); + // set the switch to on/off depending of the service status + if (servicesStatus[6].active === true) { + //document.querySelector("#main-switch").bootstrapToggle('on'); + rtcmUdpClientSwitch.bootstrapToggle('on', true); + } else { + //document.querySelector("#main-switch").bootstrapToggle('off'); + rtcmUdpClientSwitch.bootstrapToggle('off', true); + } + //console.log(servicesStatus[3]); + if (servicesStatus[6].btn_color) { + rtcmUdpClientSwitch.bootstrapToggle('setOnStyle', servicesStatus[6].btn_color); + } + if (servicesStatus[6].btn_off_color) { + rtcmUdpClientSwitch.bootstrapToggle('setOffStyle', servicesStatus[6].btn_off_color); + } + + // event for switching on/off service on user mouse click + //TODO When the switch changes its position, this event seems attached before + //the switch finish its transition, then fire another event. + $( "#rtcm_udp_client-switch" ).one("change", function(e) { + var switchStatus = $(this).prop('checked'); + //console.log(" e : " + e); + //console.log("RTCM UDP Client SwitchStatus : " + switchStatus); + socket.emit("services switch", {"name" : "rtcm_udp_client", "active" : switchStatus}); + }) + + // #################### Serial RTCM service Switch ######################### var rtcmSerialSwitch = $('#rtcm_serial-switch'); // set the switch to on/off depending of the service status - if (servicesStatus[5].active === true) { + if (servicesStatus[8].active === true) { //document.querySelector("#main-switch").bootstrapToggle('on'); rtcmSerialSwitch.bootstrapToggle('on', true); } else { @@ -269,11 +357,11 @@ $(document).ready(function () { rtcmSerialSwitch.bootstrapToggle('off', true); } //console.log(servicesStatus[4]); - if (servicesStatus[5].btn_color) { - rtcmSerialSwitch.bootstrapToggle('setOnStyle', servicesStatus[5].btn_color); + if (servicesStatus[8].btn_color) { + rtcmSerialSwitch.bootstrapToggle('setOnStyle', servicesStatus[8].btn_color); } - if (servicesStatus[5].btn_off_color) { - rtcmSerialSwitch.bootstrapToggle('setOffStyle', servicesStatus[5].btn_off_color); + if (servicesStatus[8].btn_off_color) { + rtcmSerialSwitch.bootstrapToggle('setOffStyle', servicesStatus[8].btn_off_color); } // event for switching on/off service on user mouse click @@ -290,7 +378,7 @@ $(document).ready(function () { var fileSwitch = $('#file-switch'); // set the switch to on/off depending of the service status - if (servicesStatus[6].active === true) { + if (servicesStatus[9].active === true) { //document.querySelector("#main-switch").bootstrapToggle('on'); fileSwitch.bootstrapToggle('on', true); } else { @@ -298,11 +386,11 @@ $(document).ready(function () { fileSwitch.bootstrapToggle('off', true); } //console.log(servicesStatus[5]); - if (servicesStatus[6].btn_color) { - fileSwitch.bootstrapToggle('setOnStyle', servicesStatus[6].btn_color); + if (servicesStatus[9].btn_color) { + fileSwitch.bootstrapToggle('setOnStyle', servicesStatus[9].btn_color); } - if (servicesStatus[6].btn_off_color) { - fileSwitch.bootstrapToggle('setOffStyle', servicesStatus[6].btn_off_color); + if (servicesStatus[9].btn_off_color) { + fileSwitch.bootstrapToggle('setOffStyle', servicesStatus[9].btn_off_color); } // event for switching on/off service on user mouse click diff --git a/web_app/templates/settings.html b/web_app/templates/settings.html index b6d96dc5..186bff78 100644 --- a/web_app/templates/settings.html +++ b/web_app/templates/settings.html @@ -322,7 +322,7 @@

Services:

- +
@@ -352,9 +352,150 @@

Services:

- +
- + +
Receiver dependent options
+
+
+ +
+ +
+ + + +
+
+ + + + + + + + + +
+
+
+
+ +
+ +
Rtcm client address
+
+
+
+ +
+ +
Rtcm client port
+
+
+
+ +
+ +
Rtcm client messages list
+
+
+
+ +
+ +
Receiver dependent options
+
+
+ +
+ +
+
+ + +
+
+ + + + + + + + + +
+
+
+
+ +
+ +
Rtcm Udp server port
+
+
+
+ +
+ +
Rtcm Udp server messages list
+
+
+
+ +
+ +
Receiver dependent options
+
+
+ +
+ +
+
+ + + +
+
+ + + + + + + + + +
+
+
+
+ +
+ +
Rtcm udp client address
+
+
+
+ +
+ +
Rtcm udp client port
+
+
+
+ +
+ +
Rtcm udp client messages list
+
+
+
+ +
+
Receiver dependent options
From d9063e2c85da976d6bba5bbc5edf48993fe28253 Mon Sep 17 00:00:00 2001 From: Stefal Date: Sun, 24 Mar 2024 14:01:44 +0100 Subject: [PATCH 012/127] update internal schema --- images/internal.png | Bin 19229 -> 26129 bytes images/internal_synoptic.odp | Bin 25409 -> 27098 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/images/internal.png b/images/internal.png index 13bf6825e325af79d72e25339c6f11808f431ad4..43e63ddf12e4d379100cab34ad1e7397ab44fc44 100644 GIT binary patch literal 26129 zcmeFZWmr_<+CMrVC|y$0As`?v-QC^YC>=v1p%O}WhbY}3HNb#?ba#VDx8z8lHM;ZN z``z#PpX+>iuX8>)I=I%XSr6;EpI_b22Q?L0Of(WS5D0`RFDIn|0wIP0e;%Ve1b*VN zR^I^pg#gx&l>k)?lkEU+kY0)_i-SPbG3Ynu$iRD47dd?}2!z!M|AWxuTx13O(A-u_ z&t22W)0s-k=4|Wa4yF+>^rP{n5nz8#;{&|@SRRzyJbS^UF(#Yk8X-ETO+P^2oZsSM?1;q+7N4gn}y1sD}O& zC#2r;am2?i`dX6FsgUR^Nd&GN(#bkw591j}b=#5z0wVkZCMM!na$mzgJ&pe)$FWnd z<%Ej*Ua67J%#fAE@qWv1(~lJ_&nZvemzg$`(BpHKd6dS_g*vgBbp*S<%+8d2hydIj zhUQrg{4Q5RE3KL9A!pJh6n)$g7L zo?LB0etfW#y_LUXsO@%!%jJO%0zH1`@}4i{^(n90?EakFK8NfMp;GOEVAJ}rGb;n| zZRp9s|EJ&Vzq=K~Vd{6wkCgtKi=}*yNi8-6nVn^?Tzf$-Dn;XUFQEB`ID3iw;EiVM zls-znfsd z_l){<5zQ8RuRiZw?mB&YFO0S^fF! zY`@+}fJ%RBvD~pweHM*|t>L)`ZnGCYzhvof+ zi6i`4!mjp`oF6X~_wrfQY+KckCFmh0t`w8~Aaw8t|Q&-39kCKpHL4Ye|S z1NJA7GZdIw?ycaShs7!k@*}}t$SA;A1%hxf}?urm0S17&NB#N*a?jlrp2k)6E{ z$URh4^QbGmW7P-`<$tFBGAoh_&AI;+6Pj`cA3e)d@cUz3oY-CKkSj6UgyhDzaWu!} zM~T`OYm>1~g_9uyYkuojzSdMLY^9Ol-PayzPBCM3Q|W7Z&wQpwU1TH3F(T|~M=H^$ z6s!3cZ;un(9)UnDp)T)H<8&vUdhFeJVt?w|uQq&~7>DVuB{y{yw7&B>SU_j*GSsfO zVL6R=d$()fi5;;-zf6O1YFel_kM5WP1o}zUK~1kBE_TyYSl%z3|C#OS0lBXz!K9I7 zDW|{++ti_n=sATQ^VysBvSc2KAMYP9fk0^=T;9v8vnD-vIk91EunUO{YWhIu3<6P+SG;Bfb_H{s-qE|$X3@~vDQ#APep9m#FY z+=((U>M^29!s`hebWX{P5jcilEk6K(mSy<9@oAb5qB*EghtHSsztJG6k~CWW(VyDC zX?5g=XBe``yf7_LX^lKl@zEf>+kaTUsbJRI(faHNC@4p?WEPpyC9y-{W=lV8bTFDD z#4WavNFE9tQh17(1zNH@`J{Ub$Ll^8&LqbzR;Xu=mc0aqqZf&Pts3P5cC;PKwUTht zN>#^c#dlwoT>stdD| zdvf|CZDsu~T3MZ2E|VcSgsY>BO7D3mA?w8Em171N`sesE$r{y6+EY~pUxKQ4jg?+s z*B;jd-Y=Yd@60+eCB4&44mjg-nd8am8Ls=J`Na)2>5D8G) z0w>Dd#uzH&6-E5Y9Tx72MAjT*gfDAb+IBgMudlr5ee~u$juu{pXtXwIH1S}$IT?#x z%@_#3;gIZfg&iMwgeHWMr>nd;%e*qTP8wdgt5rU9zK?L=e!EKQ+)_LuRdi3w&wphW ziIu@U@JsnCBp}*#^~(sKF>8_0Ge!Tc{^OqRKwe^E3rrcl~9Q*FXfDrV|3lB>xWc{*oNlyHRJqaTDXOQ(@nz zFW@PZ%p)9r5qr{%ozn7nYc}h}ZQw#F%%bJ`DWhqgr%8&VslQzZI^q;e8ChbAtJL?E zsLw`QU%^7g+S^sNi-QF==Ys_X=hnIrvI_sDUp{THbKXI$PfLCm{i0q+aWi)iLlb|; z28@;qRj`$+bx6wfqxF2*xB=?f=xtOZ!Yd4c_p&)c*G782 z_jfI4Lf6_tAYe% z<<=^|=m|ft;rK7`oYvdgJxOqFW$tc&_he#TKb^&ARo; zjnu<>k(^&!KUYjs-ZHC%-|XLAbB(u7Igslz;arH^l^GG;RA2g&DpL*OQ?SYvBv1dEO8r#dRP-Qq*zl@4jQIl6&-3w4N629I)18XxU#K8JC@ z020XReYv@?@tzCg<){Og?MO=a=;skzUq!GqgCE^VEUkL@3 zK7YKAD6g<_`TlbqvL^8ZzpinLG0YEcErV>{_fa0U=m&UqddS3^;9S95T(qUgVIWPFNd$~%nBS?lo36-Kda z&l5#@&Q<+$^6^G#sVD=CcZufy%iYRQ&Pv!5(Vj3jxuK&0_GJ|WWQ5}#$Q!#?a}^Ht zVTq4&56hc}xhKh%`KeEzL<^jtK=Q66H%(`+NdlkFnD{_4d0*=|` z<(hHEpeTbqk_%^EJI$<}vAF$Z>TLh_++kZC54V8a^^0?vlAKS;g&YC)s4`Adgi{o{ zQ)IURQiI4XgYEia#a+frl8n(! zdvxf%{Lf+IpHdp0G|^m2+g2>D8pvCn5Vq?`KWz*APQA9}(JZtGnaB)3-%%}#?K+Ae ztvDmoZaK-KT(meWoFUh6P@y8-HIf=U;o=yW`X(xxY|luhYo_#+#~thC3oki!0%@+7 zgRwt4y!Mi-ZT)|mGa9<25AyHIEpZC{fwRPsC34zRmFOxNrG}xrhpX&w7F9DDT3_Ci z5rG^lz86vKwOL$z9GH?~bLH|`9aYvQD2?T-%u`#|^$c}7XVqRRdwUcnTE!z8u1^(ci!>g|%n$Kt5u2yVxwv3A` z2@>;VW)yIhYIhmGIelW=5SqB+pE($V31*;h!hVc>Q)AzYTzy*cOx`>2uFu`Uq=H{n z^zqwVHwD*RqmBDU{493~-!ro4w52uPE9&pZU!h4H24`@b?z+Q2$i8Se7l*Jq%FX6F zEBI0AdB^HAOy8}{SFT{`$HJ=TSdYrAqwtmxdho!7S@Hk@id6{iT*bvAr>M};$yH;q zIbhO5%~NInCN-WDBK4-`%kZzrGK$T+EQO3(3?CR72vqaVt25WWeb1d#rJFb{B6*LS z@iQTtF4RxQuW-qJmY_C3oIUka?%ZNM91 zq9V2_cTt-kq6@=d`Z*`r`|$jUyiNKWd#IB3Ppg%;xS*it>j%^J`)J_(oAMfmfGBjT1>F%Txkq_ zPh8kY>{8{?glqz>=#dL#yCvZ~&MR+*X7=#foz zV)Z2#PN`}(52^hkgb4!4Q4^6^lfQBZ0dED6# z()eJ$vC2`lPen$9wd!&LaKOBAM`&A8rTa4LK#ZtFgHmg8wIvJL$P9PsD*hgc;Zl~YA0R14Jn zuXMO9PyW#Tvtp07B!1bUWG@O)*#_^PLt(#-w5^U$4_PN4`E{S|xG5aVZ}PK2?p0NL zvsMgPWhpr{8$dy@tlM@)tCiMYAU=;>t+{}E@+*{tD1i94`TcqVIX=8ku+@Z4Ro~1` zoA&26z;Vw+TCzQFG;8+H(yu`w-p zI3|mAKBkk9sVcf*1-X?ab!P^d)!<-3-#DTE-w-$y=f0lm_0xZWHf^!+yTk?-u(y1E zIg(hc`FN$7Xy;A7L(3Z%lo47on~I#oAOE|`LE6Wn*$3F6)7Y%-a1R7l`hXg`er6@;Y$V_JQ zD{Sg5qPaKh;iW{J4Pr1F5-s+{+I(F~{mE+s>H{54a-H}i1Th&JGQ_XMNl3c~CPLO` z1O1H@yi*Z+x`w$iQsMUJKfxb08uWavKaO}TPZ@4cx7AG@h72-KcTSJfHVe43N0Pj) zh&}O8{$bcO#^(v08Ncea59U$$;1n)*C=BXVf`6|s3dKAUiRqTaEaM4$jmopBne!Chg+wSkT12rT|$z2PSFW`#HfP-}&iFI^71w zrKsYvXHH2reuowzT3u&C$w)kkKCL37Q{yE>;1`Nppk5W=dUADdgMKY_HuwuOqJ`P9 zbP4QC%XZaOceX3U{hHp3-6iQhbb4iz5W{9*;?{rwj_vkftUbi&AnbN1cM3-{y21?Sn7CGX}-5`xEOWLO`O@zU8!Lz zjBIqVW?}!u_0gzosspg{r{Xl+e3IX7y(5P-oQwOc4K)Cj zfof?Kj50!6vGsGd#R$Ne5=-HIHRY%3bM?3C;K1p@CqD#X4v>#-aV;yqPJedVq#aam z^GnrejjTRixPPihNLC2UVy7$H&t!bwYLxuj{7OJc^+H%Y>htL~!l^x1nhGC{rd?{OwE%MyH^<4N^+nD}M z-QGs__1&||w&Q{TB#;>md>O;^QY&lpP7-Q8#r1zE_wa$aN!P53FEs#cn5|!C(wiFJ z@?RcQI>iIU=(8b$RZS-2w@!DU?6dEvWcbw>`E6{tnXbjGGhptmk4K0;g@B{SUm{pF z6>Apk3g z^tG7%I)~b3GhMPe^|@a5*RL!U#IVnyg#Bz;?O5#q4=0lHzLspTs9D5mQce%J^}CG4 zeVCGU#N~$`CJBs!9?}9Sp~;#29CXXWf1Qe~fG9KxLFs)=<*%xnieuT$pRSzkI3RX@ zV8Nu9S{?(_?5S8p04LEM6qqo8sh#Mh&;~fa>ZT z&ei7pQq?17|MD8pW2t z%qP%rs6SJfnO<$f0;R#1Tt*(NBXdBx4J@v8-(3R=0B4w+u3Yn+CJ|R*m1UjqB&pv% zhPmK~y5mJ`9YlYITp?zE_kHk`5$)??Hf}HLk84h$$#0$mYkiOw<2g_X<^g%~`|Q?( zbn41nWl91C6FlZC3_YGqG#Tudu+_;`fx+A^bWe-)K1DM`r8{z z69xCqgt`R$AerrwbU{ke@LL9-Q(UOp@x3rvK?}H)_H_Q3!IdXO42+0_B>W?o@l3xu z4=-?SVPeru?V&bRY?-hNHaPgab=fA-sNWLVbu?bTWyWB6FbBsoYq?ZZBG9KwEi`GS zHbc@{Q{Vbn`4ceqeDfL2GP;(%6dKWO3kq@gvr}VzEc=*LTgBNCRGPbJUO2<0Cl&sSQSi?u%}8mYojq#$^Gs(Hs=8-# zZ?@E9<017o`MOS!mxW~h>q<+A8?%dZCnsM`{E;@I8kUn`_!P|ccjr)`Y`%Ys?@UG3 zaf%X_b`Zy466=4By-oXUqEI>M*7;QUgNIF@E2rPIyMM5|6yMpy4r(F5$^b2k0sr3o z&n}S`bLVN1&ajc!?WO+Q&w;RYs?vHhV$H&EBfq{m4QQhtaF^=k`#H=03Xo^?P4xzo%qV z8K|H!pxCj~A698UL$08EqKKH~$^IhDZN`9FBd?j-=2e4b9R;fii*a`@S8$8L)c{l1 zOQTXJf3XvqnB_j+ekRGWQ*gl1-u`Cz3Bte2Y@Na zQez!0*m2wx2^Ym6cw~3G(8nDM-;EzWFSy!^ZA#I_oEK;l?OqQ@T!5& z;rB>ebwQwQeU~S{nInmyt9;yH#JDWBBsIGy{;Lh+$jqZA1A}vmXqz1}&>Z}F#X2}R z0PCo)WRPA}E6>+sSh^}|1RZX%wfWkScGrDqWo2l*nF@A%M(pKa){!J zCGmtr_{>me!gU6$6UAO-=cscjDP?(pK zlOaCe4%7%N{btX#j*NQip4DaOb1beiJuFV#O}%_Cf4=O2^mx&qE^b5PfMU45sKiRQ z%k`{9Dqq=?g8?@v8Ql93hM;=o**WC4M-JPj4ZC`^wZ7i_x7E2rvBG(g6?-3LVDSfT z@;uGn;6GE8zc0|K+~5L->m^4xTg#RNJ^ryg{b(aa*>FAS&5S!bh>Cpd50rAbYq5Y_ z^lLW}B;;Q%Uq_Ws-2}Q_JSiMNPC~aXJ~HCJn{7|gE!e{8(-00@ec7}T9+O!Cz^ID5 z{=flJ=hosFvNHe+05!(B(dE@XagO_%YF_@A%@MA*KWHGMvlj1YR)KR@f3^xbB??zU z^9;Y!1$UlxRB;B$*ZI#pJ^?gcX}BkHQmN}N9Wd?P(DKV|H#zblC=z28M{NE*X1MBY zxn>SRe7hKNESz50Zfx_*_V-5&r!e1Aru^_hcV~{pD2VZ%N-X{s=iYIT2}&473+6Ss zryQTImA^Y3&>iSF`4UMg-Pt1Hh-t56lKXB^L)_=;z*M-=# zJ{*^SB&y0yu{WkwcD&#&5WX3>9oBXm*qLY}x{BZ+=^~&AtRc~C1UoBjs z73r`7^qkXB^f02VqlCRyokBwneJ7M9`Zl}P8LZ%~Om5Qxr6;~W-@3fFsz#~jHS*pe z=w=H)CfNAPfS|hWbWtW>^RCdCmR@JJG&rcB^NM7PeC0Tjh=Al;+BdMJRvukAQEVER3SwxO4yg2F3)8f#V-G88 zqBMwhU)0kd=QpVo)|!|$YwEBtPew8B<|2K60w+4f=Dyg1fD8Vo@@9FANj7f!Y|T;x zO!olx-ASRJHHhZR z+M=~E6CV6T`;AiLN?}#HR~}!}@mDE?ozv7tHD96p^?P)28Bu}93Xgot66hx|d2w(if<)Z@MQ!go`ITJ)| z`}i|9lYS^s;i=pzF<`}fs;l*bNv;?+Bj4cUIbw+vdkJ5=D9|FsY#x2}rbBqCVk(uf zuTb#Gn;0^1qe53JPEa?AEYpmy3>O>l1Bbj~Qx(dvR*e-pvtj+NknV`2kK9rY_qUi<~TxiEoI| z3M^j4ZGD4+C9s7#51=F4{QoY$;+^HA*xLJJ3O@!!%nYYJ_<2?ph5@I%kgXA6x0B}R zX%;mgQDD66ZQmyHwr1;`*Lr%g_yigR9uUi;MAP`I3{-D=bLUEn0xggENXB8`wC|JP zLcC{T`hg5Q`TYMmSPJy{VSRbXMw$HJ(dA1t$qarL*;enJIk({!uWiCS+LEMyl9!Gs zFPqo5wsr}rs`dFFSxqNOK7X6F31~}Ve8F3|SX?P+VSnm7;u7Lh9>0Esxn{o@heqGO zBC7w7f+hUD+r#p)&Q5qe0cY74*~^%8PIVUhQmhRN_8h{@vLD3~{6=Ha6=JjWEHbGi z3+!%{{9a=e62ojY56tm(*{q!297?|^GHbL|tQ4s!g4IdPms<&xJ%vLOICtY4Zmw3S&T^op2Wv zGl^Q!Gu`5*GM$pBgV0e&Bx#VGCI-NiLP>RGt^_Xg9g<FmnA|q z{wX4EnIe@^PHj8Qe?^!bUWaqzCle2CdFo3=Z$Fs7qXwC|;lRfexo~KxNo*FNIlH&{ z8g2B?ZksSc6iREIccS($>iR42 zEBhx1YFn}F4XF>-YENQr4cc{<5UCP?QL!opfD9lD=`fxuo;$Tx=akqcke3d9oE|TZ zafH@5IyzJ#!;Ln?UV{gQZ^xivAA-H2edQXTjxu^sTN+Zv3N7)_mw0^vgc^K>O96j> z%Tms_uUs7q0UCIAskIZwZXfqsVW5h@T=IK`Rak-97bF5Gw-hE(*_yL}*nf68YdM2c ze@pgd=t&JvgL$Z9Gf5Uq!)W2Dh^})U+NUY&tFL(6ks`nB`HuSL&#t-s7KHlB~$zcP)L%&F^KSev$?}6cNw}@`#2#Sxr5p{`~IG%8b z6pjoEo{oHl5G)ht=%hWeR1P+cImAMoRed(mOnn#WywLQZ8*=WA5 zBS+|c1;Oo=GZ=#xvEBjdcD`B&dHBFDfN(eI?nhAKmh43XH$9fsW|fQMQz<69*=e$F z%)b)EP@lD7Y?|L9)auX`lvv>1ht3i|Z@~|0zr~jPW66<%VM6l7-^9`L!fz*0TgHXe zbmnPmK55jNFJrWjL>_<|?LQ|Ltwm1>Y(q?Yt_<+b`%3_je4Sdd>Iq^57-4MERkQSK zZh>FLV)T7y7nrP`sk~3RxOMoL;xDa3HnY*xj14t4wKVY9mo;Ze#yyCt&vt!+r8Uf; zFTXUfX&h(je&Hyt%(pM)zUR=uc$p!DFIpS(e}fUp`1{R=X)>SM0& zSfz?5De%IX=A&~Kt3~ivW#cqnwrRi)eO{PQp*bOs-Qfzu)=6dmBG&p@Kol$4X>nGEQo{J|p zc@(PytW7nB0H7mc#mn3 zBex{1CGoRNY8tlHM71B8MRUb<7aZZkM>v)P2l5pzL?9bqMxK0YtFL4 z`AYg2Wlph^PkR6|fEZ^12*HRy8agxlvU9Rki#9RSk61ie&Cl#-%RC+*z|0+{*=wOL z4W&KfWfnRy6G^=nRW0rQnD+{{wsIDj@*rC{?Uva-nL~J{ldn}7ff~ah{OYrI%k!%y zc@~RuU)bW*5RWlOS(w}BEj$t}Znymz>Vf=u<0uDp?Y;_yAC2};s9jU|%gV#@9-r|L z&gguI-1dA28khbZ=M~ynz~mh$_gefML$>=lDuT#n+&tPjO zSG@c$6ssn>Y9ZoGUYeE{i4SVLU=(vM=XgtR-PhYTOHX<)jIVlKuUhaRw*^PLe~S3O z2}D1ZTbdp=cocgX3L3|SQ%eDdZml)S;&+?}-$i(4c-BT2P#3=VEI_v~eXc?0Nn%Vu z@yaU?y!|ee7WjiK&fmEPQg4YhAU~Tuw17{`9(eT$wyla4RBgsc>P?|I2MXe?N{r$K zEQy;d*B82N0#4;F^?le(=f30mLZJ!nFfbGRt8b<;ja$%{u!?~P6&AaMVN#;oPW(L|FaDF zCoBHH#mgQ{bQY#E`L=x`Eli6C=q!;QL7hvO|KFXil zC4YgKeX6I+W&=>-9lM>D^r#=Cs{j@D#|Ci{!p($l`{Y`}j(a!pr3;CN-C8>M4GLd` zy{aWjEPN>x5i|0IOeweRY^Ngl)AIq+$?%(W0!ik>)rHn<{1C~yWQ#2Kz0W`9XIIVg zPocw>T|u~WPxp1H0}DUiYcLG3w4QFM&+ z_%FQm&c2oDx5-9UIKmva;Cxj92tS{5s1uhSmA5pYJjT$ZRi92w<}FIE$jqO^MW{NdNB|CfHq!PpUIdSy-YfdK|Gkf! zd6xwL_24C^huzIl2?jH*zMbAB8;rM4FlHQ6PBF&oyf+(Kxkn3t%^kr07$IdPBN6Oo zSo-zYb`P$PJtAxa^edyMN6dnqSmD8vPl5Vyeb}ge(Xx4ih`q{Bt2KcNi_&>Y~SQ#G$&TWbTB<6yZ-xX0<&|n0XR`uq!TUOx=p&UFwq+bs2ABZPF<!wz^2v5l!svYL(&KbpqdXYsH~H8HA})S~%e)dwZwyqb9V{nZ zz9dfXQEx|SSdd-tPV<|Oh&1Tylylu-NQHfTk5(N1q(jFo#gQkcxqk{gm&5+?34#Ok z+Er#&qtaSv#`Lp4`=J(`+E9npe%i3T8q&*8eNJfpKWKRtu% zlieiakv3*jWu$OI8$-O^C^*h+SCx|j6cfu8Nz+P<#!%Q0fVWA3VVUdNg=l7!!b%!> z@a{sVfk;W22`}yQIrA#HlZX0+3lOK=^&Oo2O<|pkc9rvV{oE0h?4{4dCI_#o{V>2O zQ-bDIU%gd*dBczAMVKR#wE5rEeWA0PU5wW?gNja}mxQ%Nfew(G08_Joa$@Flr+MlA zc~-n{#H^poG4X{(E}iOBS#u6N{n5 zxKOlW4WDE~oQdGeSG>Ob6!vMP+&ONBl4mD^xWks2aNl=_KX%L@Uw>#oiBI)Q2TJf} zbYDQAzvH;$g$!pNhBpD6OERInA3=M=1kx9_3I!ab!k1%tTN6*r%LFE$aAUK5l28cu z>#2mPozKrnuHpi@nRKFUij%r^R;~_xha>Y=)BdHSB{CbzJYoi;XRT`cu=dz({)$}d z$CCX)FaKod{>3RiIky*OR`;U=} z0|ErnZA_pa?7#L7ZUTNrDmZoS?(#~6-~D&MjutD2TC-5CJ8L)<{Fn3D!M8VetPpc> z7@3e<9cO3(=jsM!I-Blu(SMHDu^s{M?t8N22Z|N`Ej0eIeb1W!+gF8sUytSda9Mh4?$m%Ne^(uhF_c~J5zsf~sNiME#2jvz z?^CV~(xldYqW~V!z?*%(ad{=Ct5|9>823;MG#BwdJG}PH?isjhq(5eikb6V6a4H?h=X^winGPMJ94L{sAUo*9DUo*|~W?#_|)d!`#<=7%+|JYj=FHO&Xk6 z0^qF;`lsbb@?Bk}>&IC!+gzZa4-$YWLhdy<=8K*7aX)fm9p0MKb~~l)w)+^-#Ki9G=zN_d)2Fj`PXJLhZXe@xI_7HYgmRY?Ub@N37tcPCxWM$S461Jv3% zWCwX(M7^#(dIR?sHGn(yN$4p3y~(dbul}0T6*fwfT`t(;T)+yhLDL9;l^y|sz^-t> ziFOXSv;zzzxP=4hG=Kd9pRir!u^nnef$6=8h*faA_15*^a>ptTR@`%DvVhmC90136 zny)R^&~T3@^Z5pJSMR+29YOY$yf3XxlkD29;O?U#Q~4AW`|PH`j*RE~q>ROoHt z$dLmXo>Uwk*^wve>{7nF&PLSdcN)c}j}rry9#VY)@V2*(?kEZ)Q_<5|c=|;>x)z;h z^<(fLtXX2dvx%j=PT@Lvx!1|Gbf07k=5T+HS-(pNdJF_jsAw@A^yQCq`nCISG@u#I z`&{$bR!;#ufuZ(nV^1KT6@RXz3^YL$e}M&c-p1ZDsdUFZj6;C#5zryr`kPS39veUS zDBF>}t&Gfxjm-E{2u*`?#s!(tZnipK^Pt#e_NmC8xm}~DV=sn@4M*3H=7Y6Y93x-? zbE(GRg)JIK?WVSZU5pn+zpi=tcdN0qAl5yB)E7Ptt zSzyiz^Rhd@_#MfQD^zri#UsPOAv<00UEr(ya}A=R$wzVKx4nK^Bbai0yMEuBOcw7S zDc;x{Jd;Dv2m|sEX)p1G-8OIP8^y+%7v5G&PRK0Gr{X+y9RIZ0-G!vaUAwfu3n9ZL zh_!f|!C95h>J;$Fmz;r^X;&DFE5;RRhB6y1s(R(IT1EsP|{_ZklF{tFflKu=Osa zj-W|AuT8L(T_z8p+EIIK%#%IS=K@(K;(|p2Tf334Fa5qF96%DqP>nt#R5Et zR!_NAhlN)hT@E|KBy?vpgl?BqKECi1kk@ll_BHbsetj531iwzK z!tvbculYRTc8S@7ROC!W!sg8#2%6dzX#!@dos~d+DkWo~H{W zIt(ZZe}S=>^Oh;!&d~{H+clsKQeK(W>r+TZ=3FNpQuPA1938vcgfw0w(zTnM^jGr; zc_aX;4Q1q%h%03nt;+)v_z7Rc>ic%9Bg!f2@fCdDM4D2mYfo16+rSg3TuQ|-gN9+s z8NZa~L6q0kP}j=jP&_@sEN0~$-y2t|pF)AY>{tOHm;G%14nKJPDB5n}R@9U5m*i_QW!w%p9`U7_@o0v&ZeN5ct~CKn=0h-S5a zP^*pKuWAuEVD(3`p08y*xmb8~j{Dpi9FsB^raU7Hk^$fW(8vL_4hj4lm~sP`Yip&Y@Y!v!lUf4DRNN&ay=4l#u@ zU<(M1Vpwwn0M(`omCMJ=M!3DU3vjp5x{X9`6-lK(`RI&-!F6iP5Q0j@68@^ z0-8&pP&0Ubkp`1#y&UkVstt`67^ke`HdoKQYWpk7J!)9?Q?%h=be=@Fw}1t@c^PNK zJlx~mfIu)dV;6`X%W7( z8!e-RRaDXaDcClCFBrtSs}K!(?24UVagor>;9MWSGuFgZ2LdPaQK&wL--&Z8&D&(k zW>5QC!_LI{$mTc8RR^$noKt=z=ySQlm>-;U#pJNO9PtZt5(oOj5X_Xq^S<8sEYP#u zRb&0hJyNBaT3z6G{{;=Botu0zEBfu@Yo}Oi#J|S=naHSF*c&HHmVCYtxubdRh4c=d z>w`Cys4@P%OnV{J zYlZnAJ4=iwEryXJcYL0sIwYUG_WxL#ug$jkDtQ53KpqCYqGI{z$HWL>ChDon)3Gt^dfK zF)PzhlIAiwcYEjKx1v`Ncaf{q!Bh@o!lab+`{)3({2*0p z!AqEW1cfO}nHoT*1 zhP6~sa=4uVO2dA3r+7d{C~q-ESLX_cYKQaI1o*xA7?_W5>a(7E|C8~{6EgliVTQfw zF?D-inPjJMN&$+SOu%Z@{<+tG32#t!muXNAR@&ny8>}tX+-N!B+L*Yn(SWzhJ1Juc zhxL;2-()ACMrVWb%38KxL&oo28M7P28W4#Yxv>v`UKzKk62*;7ST=*E8~vVaK-@38 zGmCwozht0AGv>GuEi`noX5%p-#+NR^)0h-h|9x7wjgpucK4K<7L;QEKQ>*?-+bdj9 zyg72m(d|r|yx)N8hM;L>0uV3NwHZv!rR0#`NB(|3>Lwe}iV5u5^PH6v#?HfpQ_)mw z5OCbK61TD(@Y%?3ib>yP-pJq6iWpek=BD}|o|}6L;0)37dpO0Baro^&cIV*v?IAQL zFY^Ho!m;F@5Twc?H`DRPM)0?T0Pg?-9E8>DjP)!qXP{Fbko}fBTDb>m;2W2|4!6^z z_@l)yLx>@I#U(lvOI2>c{vs=E+ScXcpJ3(G)-a!!sdG=_hkth)T4KkKh}`k=pPhK} zpZt`^8vHyubz0VPHL`RixBl%h+B&e$$B;$h#AdW(e-K=o-@*!W+WlOftb93#Jw2d* z#IOCAKBIuC*OlZbBmDNQN@<*D-;sf+aDRbx)zQ_^tqrd3#wS=ma`#I9=`cT+IhDJj zKVKxay<`>_gs^XT=qTw;(bpNKp*w-4jO`|`MyOD<2DNr>lq-ypl97$=ExBH^NmI_{ zbt94l@9$0}MYg-D#kNbrOM|7Yb*MFZC}yZj5oOafG(H0|WISD}r^kx@!a4rlgfx~8 zedGxbae>D24~6TCm}%%Y$*4HA_1xh!v}DB(kPeln#3fZ*isGtfJeMWMM_ZMF0q=}} z?H;;Wh?#EoFw7*1%-0>Tf=f6jFudV8mB{Gy&w`HgPltL~P~Nxc5T(3&K<*U*`8BDw zrBu;@pI_TQg|5davUj(B8E{-|BANf(Y~_)Vm%iB`G}MH5QuzVUA#LqV(b=D7zZjPE zIkc?ySTAx0I(zgiw0p7BcdvhoZ<(i?}*>l=rTf+WNEFv-W^5g7;CRsJ%hRoTlTm3gz<^jyycLK8p(5UPg~wM4VyINaspoCoZ8j6n?4ZJQhN2 z2Z;jOtpp20htJk%pdJ-lm8eadX3|6%dWLopDn?*P9$=JAxiqbKdrH&iTq#Vs3;23r758a0#XH~NbiCO(mRM$rS~SF zfP~&dCy0R30vPEKP(tWU5VA-UK}u-Sq{IEib^qt?xx4qizOR#+GiT0ZzU6tIPm&SY z=PO`cS2dJ3L6t;m%4w1v+;1S=sd-@ zLrmcA>35et83Iv@45`S`N_#x05avLBk0oTIc5db~?++N;?Mkj8_B=+5JMV#7bcKQY1@%v|mJx_>(p^J?4zq_0hR< zu0R4WAK3KY(v>pL(bg*gJ_pn4QcE-JMBB!rzVY`rmg1SKQyw7EeyAar^WUCbr@)yJ zjD>;%AD;9)U{7b)wSNAFu|mNolcO&$R7pVh10S{{?QyQ5JAzO<6n!yIs57X!b3;OX z6?eS6%n-QFF{Cz?C~PzTnb9v`K!(#R%k2juY?ZoxHP9irHJ~2-(T7Nu7FhzdD)Crt zjg{v%GPMpeoo7mgP+})J3V?=j>z-ZovI95p z4vj7=(Y^A#qikS|mblsfEbSbDiM7w=qNTWA<3{RzMxqXEoEC?gAxZ;dr|&E#uX_dxQTb)%MpT~jk=AxqBTqX>4DLf$d$1KkpL_yOS1aPmk6@8s!`ci|@HHa@NLi?5vWNH=@ z>zOX|rV>7GDHj>D;m@Ns@@Id_^rl!|t$q8>xgTf!$!7evLEzo3rB+Djf)rF1_pgSO zzfxz;Cwq<{v|Pe=>dCb~@gC_TkAbYD>(GLkOvT-x3xZU?Q(wiwIU}V?pqr3-?}I$1jS}XWJ6rZc%~@0Y#BO8~VRFPXMKGr2B_t2}~G^ z)@H2o^0yO=cu^Bf&n6p!}br?;925h(}k(Xv#P3A~6#V{bp6rgstcmS$fO}NruU*07}xE^v+*~O5=HPfrtceo(wXc zE-+zT|K&ANJG;`C5#`@3aS>ZpqO^(rkjPaQ>fu*|o`QwP%Z)T$|8e`ZA6 zC&Jqj^>38=MFG<$OQP$;OvIGj^{?XbUOSI}0)f0dwZC+XtC^CYMtlVbLGSRh7@O_- zE8G*4g*4O2H=d$zkgx0XGFR)P!CXANW%%D^j!(Qf=kUaoGb|&(mFGh~{d1>)i?O8o z3VyFigKZ}x;=xx{g%St)#zt1}K43GB!H-VXdPFKTuj|qf+=wU=w|6w{Xq}IP#kcjh z&QOybmPA>Vo^5cn(v^)F9+RgLdM*Zjbo3iB(Xq!vsx-Dm2}$6?a9bJ$O7wlwPdPu{+Nzz;uBdNQiwyw&Upm2Bq!D4?>W zxWmZ57nyGHcx1?A+HZ4zkxnz#%>_sqsVIIpY316hl_NkYg_cv{0 zw!s8kopTE$bg?o77Hs^0hwb9bVWHFKPa@|x;_}dx)~dZrudj^!T)7$OGsP8P9;jMt z&A+1~@iVt(Hoh;JT%R|EB&OwlA&ExpX|2FaB{D$FFY@;4Gz(qUYGhqFOi%1tm3|rJ zLZh^QT$z33iox_o#DpGXOp`ar_wL=W8}!X_eo{}G_1&d^s#tMu!ZAUCSnO9OWs%;) zq3`ZEpJ1wbesnkbHE=f-_#I>{%t5o9(_kkKbFVecIi_2d^!-+f-|iR@bop<558D3+ zs%pHg@2Ih}PY#$X+UBKiB zbbbuoklXksQ9PG zVIR58Zk}DTRx{tY3UBnY`0`a(F1IA$uu2Cz$?enUG0HBv+BmQ%JkUA2RxbO1R8@u4 zN+zgw!bYmRWHcy8N1(Lw9B(*ygc4{qB=cmY54+;KD}tKjf{wSY&1LdPb969vm?}If z_$Q8A3ZE|&%t?EUE79wpQ7#e!@x)qvr*Or@E^1W&M6+LHT9q5QXzoGt$5fQo7HRB5 zdSP}+w({MOPhh{8e!GO_`&OC-Tb9Cf?YWsJ9-5WkGxSWbKl&20><^o#Tf{G8!ZECe?i|{0Mv)aaQSxNN~O^c|)&6ero z24I2H>A@C#qo%niP-Mu}lX_6l`3E!cNcTN`5x$VXf5WH^%DE;eRWe%~YI!V*lsp!j z3HcOR3Y&y=yXj%HCV;|vz9trX&`n+Ko|CAY<6xxM@hRG495(1(?ar>KEKr-g=T%lQ ztXRES3EC+^)@KWFK0XL<(OMZ= z?jUh5xRez;?1Ay?wKqf8%^WGO z@xyWe&-m{&A{_LBQqYWOxB`1L9Bs+CSKXO)dPC@VtAUktu9rf*N|U+Z#W@+_W*Lk8 zg~8uBzTYjOT}7vf1*Lepa3=WNfkP8W90g<` z-dBJ?+p5Eg#ffYV@{S$LooxI6G^W4beIV}oyqS!JW>GwB;gd&tz4`c7YT4;N8hN@J z+Hk&GV$-&V%PiV zXOf$`F6!O6(E&k#Z+BMCUZRBk5|6;oRGCi>|vKfV;^NL;f(DM-Y_9OYp5W@=8 zHqta`<_Vz10eAjCC|7^ljQ*n>fv4=h%QydU0P>+hr7rIHDc+AXdGYdFtLl@96ueF& z$~FvEC4YtvK7=g+-C93B=RMGRhCf)75OG}D+Akvqc<@eI@GMe)102<)sTP@vcw~Y& zW9={aSK8WzAcE_Dlc?ad7O62rytdNmH>3#rxdzOrk=-+I{H8{69{(1nAV1o#KpyEB z^l;>&^hGAU)3yQHRXX{+uArm+FcF~pmV;k-qSFXcK5@vum_!%1^SNXN<9Lv#=+IJOVtt$a zED5~h1De;B{7zn`ZEGoxF}&?U7Otm}3z#{coJQg2vOmo$bc?hC+EPNJoj7#d!sCwbtcn<_v;oXJodu+Carh1wM*71!hCu{SM|*6G&FAG@p#)6sujYXb?kWVR#aP zNr8qCOT<1PnfQE}E@#3f3qxnVbYO~kmb%iRe-B2tCw{@Jg=yb6Ni?aa8Py&A0fMrlvw2iqiIG+;Li_**NDC9_d!xU@B*Tv?$o(W%{ND`j%hhe z)`!3eW*eN7er`iydx)A0g5S}5y0jeER7ur6Xg=!mH*(*&xuxm#>?>gAUL+^k6_Yt4 z1~T)Yk(#oLL YKz%Xu=+7T;=Vj;5@ubsyW(P#&$903Y+yY*wk4W!+9Ax4zOM5&S z);s)e_SXJr`?c7X`$5HKjT%8)Q17wbQIiA5rW03o`kQ_j1wynKj*jFwqX+u{k6v0ZvzKRVb^NF-Wxvp$j-ltEa5*@5lgDpQAUG)$N`@0IPHZeQ-xZ_lIzken zNBD7RX+=vE0^zgtw#S9+zH#-T zmK0!rv85J%qH<}%USo<$k}Y^!aC0d^ zpZ&ZWFq3Jo;=l zlYh7rm8wW#j}OW%e3d(Bu1c81v=B)lOBHrwQuVcTlYs+Aa(&Ngt5^1nOB88XnwhrE ziA*1Tr2mmJ2C_|@^U4rlQN(pu$TER@C4h4F9ET8$=3?#9H>mpp-?i^i!HC4y$r^)p zL$af3SOtmW5EEch9K)2m@zMy2lgGwd8@B6PTsAGxdgqiOO6V=a0$9W zb;=U^{fNSg=%b<#C*#^*=qBrO=t+YDQvh<@wC2rMNXOD>9w{9ev?2+co9tTBnrC@s zq`NCzVb>!itb#ezb@7oQ0CMa-{LZ%D7GdbLZ5jb^d1AHsAQoCdlmJ@aGaT#8EQB!<(MY!!Wk_H?<2N0>K4RQ4!eo?yh z@n`{x`ILzCfj4sJ>)V2vl|SgEzizea`j!{U`WjGMSe$IM2P`)yq{PtVFIm~L{K-_T z_TcDVY8e|V`)$U%znYc|4#pI3DX37b%c2CU1?;%(=|I4>DQpbKAJV)4_s4m1#u9lIE3Kb5D)nK)<+plI}nJd zO zPflJ9^PQws#Ld^2zzWI*pt4gyh=lz~9wUz71bO;Z22Z&l;u! z5!f}d_IHqb#L+k|72`4YEs0}=}ozG=_eaT+)BFBUs;7<7{1Z! z1JK^r6Xiq=&X12?wcr0{G70l<{4$tbYIKp`HYlx{C0Ni0argojn%vl;0}+YgZkmx= zS^m%;%cD{$vMTmqs4LbNjR;qUxU&{$qh6yA?hdWffs(t*T~GC`tyO~tmxZEC zX#~x^q2H>oRZ=WN#U`4^bt1KSjEVyFC0s?2vMz2O@x?S7Hshb_Pyvd0H%?o3_tys&(ZK~qa$Wl z`XTqJ0rH_R5vtM-{dj@gc(AwD|eXyb5eJ8SYvaR&?`q;OEiSs9c-pd8E3% zcNhp!hJs4h#0ge!qHykwnMqUXOO}Tjr&u|Mfg9_XONw%*So#C&HZ-f$SR~Tk+^y-+ zZtMD@F8*L`@@Zq=cZT;ub-9qt{8iM@3{#t!t6-`k;VcSM~Hb{OYtO1~Ku zxK8a?z1gR#tC!NTXpo?b%0~3)>g5GdyM9oNGL`htkE%{Ysi+!gQ|AJs1-%_!?nQUU z4l!{_Qlh5FL<{3elp*!;#WVyxVwyhhURvz`=&dBa1{#tY2!6j6FlxAbi#V(DZ6giK zPvlE<9$D{`&O`rqT2l-<33#CN;AC2pC$OmHXY}8C^7I^2dUW|E9{N3T-&_Cq?Nn9I z($f!yx~mzb)3#b6!Wl*tu^PVzFHC2+>ONV;neefX_C4YQ*R>2FICYu#U8HKZ>6*) z-3_5wP!E2=rMELM&!Q*yU4+s-ci|npeF=qn++W5@TR=d%6mp$8!0 z{k#HP=0m}}7ytWqV`eaXFyqTFGH7~l-S?zo*>@$+{6@m1x;@m(IPzytc4D{ul2$LI zla7RG=S0rEmHOE3w(3WmNIqQCKZrh8DleyZ9><;sHYXmY+H7!1<1I(V3so+DQ&k*t z;Y&$XD6*bxByc!i|FyuF%lq+>*8ai1Zu3K%oyXfY^AUzKmY#$D7~GZzXUwS1p)9S7 zvR-)K8n~lAleU!^qL80THloU_UN&!ZG*@Wair8+*TEZd;8eIlH`?vdqe0N`ui5&7-ipK#ZlHRl4bNkZdG)v}Df2Wc&X;{@K0gv6&yV#eNY-Y%+;^^3!F>?J% z9>W6-?2KLAg~H=q?%9xjHX+6y@mlNIhCap`x-hzm$(h;0q+b#dL?S2H&0_0*z6GaU z>!v`)06l#WGRs!GKF^`=+!aG{;W42* zpCm6F792l2v%tn2y+*B55cL9sC~W#=W*9pW;M1};UF0ya7v-sEa~aJyqms2zxkl$+ zIpuQfy?ws=$**?gIf(P#vD|F#_DPG;LdAxvX({7SHQ3{WVw`jE3HX_raVgS7i>yVP z@56%ZW?>=UMd?xFdZDiUr9{Z~c(G~%Eo+MnlB9gsIwdY<>aoYunu9#JXVvCII4@6i zW5}rP0)OMS((I#P)or3?SxPJa`#2j`3d2eJHqvF(?tLDBr&b@vUm!?4Q@2*`0jeX zO05pD$c*xMOU<;Ui}AH+L?|jU38VY8J&+X}e6ueL) zXA&)OwM zL*eOb6fb(V9qZTrF8Q%`*=jIm%oN0>`kYtVe1|f@V`c&FClPj1>$v#T7-CPQb{=nh zdi$|v<=Zz2kMXU z#}X7bC3=tozt=;BG(l%2zG0^WBdK}nnx-0tv>qJiT6b#6ixW(8!lZO0d2DVY0+$s; z5p+Tyz8(29i^?j{)eQEJj~8FK9gxk(xSy3FF!C>Kij*t@^~xiL?oLbT-*@c|IJ>}L zqWM}?v--|MnSw;#xkPtfADp6dxB)xYBGtN0W%mu9siVfCX!h;2J@({n8yC4jvgZ~kOI4)bbnug?kEeZB74+u$fg5Yezvl4kM1<0v{XhLdmf@<|=}KULHz$$pZcW3> zAJ*4jQ%mOZj9->mqA2m(tkOt6mDTOxDBBT(`MF{HldMya0S+$9pC^u}Pcs zd0;YANoqfq*OfR@vyi^vK}>&33XUNs0Z2-x&_`YSf+J* z_H{?9o*C|{(l93|II^*;7{v$znLjo&%qcZ$sQA+I9zI(-c3JS)H|84Xt;xeOoU+ z4|nA>K%kP($#5E&WS}p)>W2a7 zuh7ZmLt58hVY7JXc$)QDaExP(e$DTd+)V?37u_wpbyG|1cziSnB67LX!XU2#n>^qv zjMH+Bg^n*IOiywM_g&Vf_Gef4kdz6C4KO@1`xJJtE;=(~I~hRnP%{Np1gvFHljfF{ zcD>_vgF+l%9tYXLhghc-R5WDdpYw{%)qsT3}SbyxxPW%2^s?&>Z)Z(uN+ z=*@*v*CZv1^ElxW&>r?&*{Yw-KiPSf#Q(1R+W10RQjPsepKk7B|7789Qq#N}8{z|k zeb(C19JO{!6`PY_?O1-ppQWC2cqW*cu6Oytqc4y457ay{D!(B^UTS)ozWe1kY#p6ip z*8U5_cTW9pi77y0$UAZ7+M`e5^|_Dn*2&jRe^Q?vzIc8Mj_eq!$zV%YBz|0faViLM(AcF+ooo=F!^bj(QaN3k|JCwLriYH+e+1t${g_$-2OIwM%3%u-7>M6 z9j72_TX6RguVhE4#B_<;=1pja%LH>UJuIrk33|h$a>#boO7$+zh z>WG_WdJx4`*KkiDH(u%z#4QetXV`Y*QDP)7TWiLEO0p`tUxrUn%X`3h#?2Z&F)DTu z?zRl37Q&D!Ix-uV&*8X_GG4RajG>kJE&8A;nW7k_aT??iL zale*a=Z_}a{A{z}>Ddwj#u>s1$=CGQ##!hHiQ}83`>uNL`+5?mN1aQbm}FcHk40i_ z@p68#(QP&A>Yeg!-AdQ7hse-uGii`Ga2)YKLrn&D@wgi4ZSCUqg2UWTIwYbit)7dd zW%OPtMvtD=I&M4fgcWXIF8YS=b0BIRNYc7NDuDR4#>Kc5{>*a<+b4=7ZDM*Ey=H#2 zfOsG?d9V?U7!>H?=y7@1UOh(G0;a3v#cS1CTzf za<=%_7?U?IoK+)?cv;7(ZPL0ijq0Ab(Dkc1F04BE$(w20~hVXHJDr?Y%U0k zHZ%M519O4fwh~v_C8ud-Vc%HyamCm0HEq)laQ;c3<7&fUYGV|##SJ447n)GZi-^LV zTs=0OdMlW9lJfTOH&L9kz+}9=K$CZQge$yj)QdSVlch(c71s(p^7Uh)aC*?`TszdR z3d}}7!?J|RvjQx70?VhN^v&SsnL=L1z=v4hhnz{$3cPt2_r_B@1I02!$~5HogZ53j z<;L}oRn={R782CnTB26{cN|mVWqVh-ie|4y9aIM}m+j8XWY*}7-4SrgJAu07c9-89 zUIG?MS(xFz)Na_vPTiSYLJGc-8l(ULCbR_I!QBUNObckrSXFBeaG=JkbIf*HpZc!; zZas$`F<`_mpajqP^}+G281ceILn|(Lp&cq1C5w@9ia5d(%LLw!z?C&=o0zQV24^P~ z_x#4)BwioP+oDI0;s^QUy{J{V=@oo6AGN9KEYOzTX>+|Cg_y~RqGWIhw_xucmDnqw z9`k+T&J(g5*?0w)N!`5DwIaBdLKxE@Esx^c+uN$n36CEOnNvNt9cmMgqOCI>i zE}j=6GwB$a4$x!jhqdh4bA^dz~rv6Ng`t1hw111q06au#AhM zwa(87tf|Llr82miJf3doveX4N>Mr54pR=QYE?fves^++d;~Yk}Z!;*;^c#&!d$6Ie zeS7p@AVRnV!{x|TtSuRlau#p zC5YfXD&_RqwCg5${J~}5?s+ivNzg&>0a>k1TcEMpaiqV|<0=$W7C|h&gaQw~3_R-XZdWsvz&L?wWHoL%(whv`}b1vS8E_64P#2nCOz*!*cMQCcji} zG;M27Yx6b2G*g7BpglmiJR`HIf>&(?Gz5T1=bzfl7P4urBU0hDzBJPc>?2?fQ`U~3 z+{&W{_j^%pANDsTKpQ~YJ8jb)q^j?W{V~IUwavc?8O}zizYKg7^Ww)BgXw`Wvunl# zS5+~7mOAc+iH_c34T0{~Ps%<1|DSf$lWFtu6XC#z+LcSFXG%%Vz9N`_lvfk=bGLXs z`gwrIt(gO_a+4m29&#tHk1OXX%tM*!S(C6y7F`h#;L`Qy^zDS>y^9K(Hn+!Z5bD6)fj9KYD zS~tB340D+c(6ugr5H1Fs2rypIC#5WqG!1Zt=EAnEtvz?bto08Afo|KFJI6At}X0KUI zwTTE(Rsy9BwBEuk*aVi%Gna;n^JUW~`i+;pcy!pE=jdCY2ls%U+hNNcc~(TsT2rXV z^GFyF)b?EI?w(S8*S_~%bua9NqPo&|PD*CD#po+Xqh3v;4$gJ{n8NCY;_W2sY+w6h zx=Y7pzs$ebA2`>C@K*dwmwq}moS~8>7Nvx>(*MOkb+{YvUzTgu!*bI>4 zpb<3LkDf{ugV7Q{S^>W2@c|xoVeDXgV}S3$*QE>(hKeqwd(fU^8P~L_SC0q>ty>Nr z9EIR;-T{g}eLXCQ1kNwxWYj1~rDc%WAS%9lx(wUn&e%L{i56+P0BD}uJ*zF*XsCNi zIwg(Lmw{- z-h^314=2|(gwILEwFM%Bg!L+Wmv}ycfa-Lvs*|T6s;ImQb}a9%wI9-d^Yd>vWvGXD ze7_Sl6nO295AdO(m1>Hj!3#MB+~68>jwdOzLg_4ekr-QYl+Mzxu?A4nmT+Iomayha zpn(F63IeTcammf|tvl_o+L(zT1{&xUEdI~#t3_FG3lECbWuuL4jBO1gGdKveL_quy zG6eWyC$-YSxsk#GMU_;XwoY2_E$>mAo2U(_CN^BKn|gbD>V^3{KeCe%ypog}97+!~ z%e!U45(C$R0cMTc!vEj7yTG#X>r&P{II5WLZ@~CM4P>Ng9d>eNh;wh)e(q8h0C+Hw zc${QN!Q|d-qwD-{e^Q`dDhN1jOpBhf9uNK@oVW!GcgujYj5S}1&;OB`afKQLm3=&;8k#i-7hDeHRYMi5O55Fv`J|7sWDVbFJE`?%I zh_bntb4>h*9#{^1uV!-4G8vZEs`hsTM%d(#!O0H!F$MGdL+Z~1A+X~?^qed$Pi+&qS{j-o+<;f?jyB0< znWlycVj^pMWb62G6;RrMlW!~zlC5aXIQiFqd2IDcUbaJ?1XvRt1NmpI%&c2;B6C!p z(%8mSLJut@3U()rp18*?j!_Q3k@tlhc1M!Yc8@{MEXoRfS3Otzt^p1q-kw)h1=Kum zR*fGT1G#c5UC@7ksclgoyJkpDk3~5_ec}&Hav2rN(MWXc^+}X+0*(w0(CBVi=j1;R zx~EG0c8ki^p0E19b1I7{fHzV&N?IHUT6}PcJLoBXqN9-3pf$+qtRoxz281O>kK7>2;XMDV)EZl@Za{+^P099kDT6Wzo5W-i|QpF+U=H zqzjC)XciP`g#588*M{8#E#!pRA^%vF?J$A2W{%4bIKCcMROP7RI!kl9Na<6yth0or zb($my$+T~FUBhCQs|Tt?LQhQzBMTHoUv^5kdg*SX~$&=CX(2Sg-Kaq zu~m**f3m^I4=QuadV2Vlkp@9kg#VYC-tTHX3>-+{la|u~Xj{lk<@+)1buf;z&p++b z_iwu#wARN4hUo6J0q)ehrqx1!UexdQ99P4Mo=^>F&khlT%k9@>j> zHjsV7zcjQGQ8ml0p9X*c8#0G2%KI!vy?(DmQDz1>6_7-c)d_4d;_ad94Ff=^DuI0^ zoIHE|JTsvpr%A;~|5Mp#>dC`i&$0v@V@4o5@d^oEt~VbJ-#fn3bPt%@B!Ha;vE@D1 zDK|=>F&;gt7!EmHFR=|(Pk7lqFm92GGt$L80x&S=nL0Yy6wyxc!!l{NIg4{~j0Dz( ze75wym1Xg2$DXIOFg`yia6GV0`Rohhe+aAZC_q^Gim2bTCO%(@)n+ZJuuHqR?#?m! z28A5eAdc+|)?j=>&(cf1=`Xsa*~3XuR%|9b2v1$9`lYz!iU08?(7SR)An(5L&P+}| z^)~gLjO1#FP0Y2e9;;Eeg8I}f3!DAzw$y91w%;IhD(cuf!FCHSxsyiD9brtcJacB_Na*s_FJoK$XSY>ibyAD53>%`jo*;D#B^Eh6ii6r3Oy9{YUW5-w=08eZpL}o+-u1Rj+`#Gbi>VtUPtR{3J1`)^!PP_UxXb-CgRB04yt# zwkhDn@{6ZHIG!B3TKX1J6tvD783~kEIlZ&bjkGxn`YN$Yz3yUk&3rdY8pSN+o$I;a zKuftuK5KW-@pT-d%63S}@g9%nH}Lfs2Iq%bRkclW?OlD$^>C4YX*aLhdZC#D4LLc- zXn@)tt&*MH+t7PIpC#@_~Yt0ZgIz2UDoj9zSrb+Umcq zRNcNMeCjprYU|A>{IAUT(WSH8(w-ly>CJCa^1ik-3=$@TfpHgyKC{AHhB_YDrW!{B z155Ew(8VAh#^Bmb^!|@KQQ83M{s&sPxUbiunXJ!UC;N!y6SJvK7st*0a_JXu0y^`b zw(n(h#hE#Y{`G@_WLc{*Q->k98adWz-IcG<$x!}cS}wvbQz`EJkU(kWC~@jNx7uHT z@?p?uy<+6jqjykxSAeIk*WDV<0_c^WdOPD`%+^C4K~C7RFYvkJCPCShAKWmE};p!vQ&~Y;3kzhoIz!fSmuSND%*5o zQoyxs?Nq0V43nLh$y|M4o2?z4ODn#q!J*w|lU|@gjzBe8r(0(t#+R7&o~G4g$CHVx?~Q_R`qLQl_`6O63<7-O&yL{2RCKJL_!CidS2aV1 zf05l9i5~OSTBz)JgW>H$yvX$beg}b$dd?yki9j5UnduS^UJd~80I+=)uPMQhL+Xt= zA4k{`qkKemha>-w?T^Fkv1?J$BXAP<1d*O_HxH`S8XZKLgb(SCZIGG|2+ZKXfqbtgPdjT zh#uac6RQD6jd{auJrJ=sGI~D`Wu1|aMZ4$}zDDRp0T?N0(l&fx>lc^a|B!$ZW4Ic> zn1EPbYxU+8)}RwR8d3it74GWOg($c|-OIPr`n>wF2fx-xd4W~{fj?F>BN$}-_S)H? zAmQ2I2a>bF%wVk9KJfQ4uC+@O3LuJr|1E$7dC9*U6bv?zbUo)vr1NZ)`gcCce~zPi z-R}HT^|Ml}X)dFDwwY?>hh96r(RdyQ`A<-KiZ=ph0}D!dz6UasL&P=2s)XY{fX&#G z+*U+%))Z-n|Li}h+;zYP*nx+A&m!iE7*DTwvNN;(gLxtN&=o#9G6kM zyl^~0Sq0F3)!YZ%zjuz!a$t7pVRD5Hdlse?o*Kf}zA3KYf5@XB_Xl)5BLPaZL>Hiwk#kzE{_*lKu2`IRr}mM z>heU|(vL2uv}OLO*Ezr{)4o>VD9LHdlmk8%lEmvAP=cJ<)&nJ ztpH@<3FeyDE5Kq(`%;;$vGr&jRmQ`LA02nVwm%|Onpt3c_}JtybfvridT3PLRd*3d zV@JeS(OQ2`ESjXO>C-a}Q_Ic5g9;ug0)3KxD8xBPeiid!xYVYRL`?l|S(Dk|M22ZP zqw?aQgD--4j6es`C{C>%t=-}1kj)KA$cLKgjw`Q6XW65o_u84tq^yLj?UB`CspOr! zz*?~zv@rl%VVLK=UnqVozo2qFCCuQKoqi}RPMC;RVGBu18~9Wb>vb~EzNl`{#E#3x z8DIv_&DZhcD(4i@zqUvO%#DD+dR2;+dU47Yue-6+1FZWXK{3b{s2Nf?N(D4~@M`a% z!KLTlc4w|QGFd?XaTg-&XgfFNOEuojPA+fq2efWvQtw zP^sN>&A@dQ?U4f}P&MEWrG3t-&`F}Eu~MH{%PCMP&Rrwyj%W+~X7fREq-v@RXYp?B zF+Wl5u`x;QaeDBy>z|+TUlYh@CbJFh_#t*nNTXj(iJxBxc-A$#XJO8+#{=Z_-z4Ip zj~2(lx~^a$(T!<7&a2h37_=s>V19zOWRPh7^hejdX7F_P|Ev$M7Q?j*wJ|Rp^Y2*n z&$yc=I4$-!XmxK2e|+?ZQM29?KTBI?3{+%QgFUTu&^1$_kGtLE|5rKwI_;_g7||bD zXAb_AP=9(gIKkyPI=`Bhqne<;!czM)Hv#5wOw4~(V?TW~>N&5`Lxy_80}o#S{tD(t z?SIo0yLl%XD;G6(rw@WYb+7?)%DYcPNk5cv`4F z?SdsN$auI}o&H8^j89svMY+xlpr5qbvh|nPmiz^^gTuzl0lPS`QJDa`$fsXu-3jS+ z>q*%d8nR zOHH_K7H?q;5aG(2Nmjwx%R3Bas)H9Jv!2e{$C1w?)Lhjjs@*$i$J~}>j+su*_EVj!zr0ve_< zyy$gXpuH`$C;m`f(tP*C!lW>(V&wk#s4tpEj0$KDN=|Yn3g(gDQ}4jG0_esRGyxB! zPb2t?NpgNOQ*8O^ujNTU(D)yht2Pz>-T7zB8f=K8Bj6)X5C$q)deheZeI$A1Er{v0 zaRA`jatx`P>v8|AszbvmYNozmYfcyhZ2Mrz33qj%D`IB)r~xYT#71%|V4&AOJ(l*oW25xJxA9aX<60IvdvAgy`8}>kAw=J+}4Hmb}h^0ZY?5%}7+Q?w@d~A!PoQ z*SY(?Xw1|MrYG*AYB87>4o$z({Q)c>(}wpc&O>h&K3kxFKJO{df53Ez8%lCKM^{y~ zq1uQ=@`NCCj;g!+Lqr#S(EjV3)EsiuolFNR3Y-u4=Oz#|);O>E&M0Ihxee6`m?j8iEuPRkv$zg4C03t`i%I?J z{JrZ)uQJQi?zIlu{oN3Eweo_yWjRL80qxHm6K%TR0%)n6jzFDhS)g4|b;`jZ8;`$t zKPZT@6CHI+Ve^sXX^}x2owapul3URG)dximZ5b3q2_^IrTYSk!pXkCX28t^pcZ+Jr zpGZR~+zeMEdk20?3P!L`QZn)I8omsorru9~g|v;C|Gdn!ztqFB&t`(htksc{+MD{6 ztRTng?$Nq>n;E1fc!BcE*Q}cEI-$guCsuOK+kVa&*RjMUHiII}C^ci1!A$xE!#TAg zTqB@_e9FJgSMgm{eSlXhL^R^Y!i{emN_UaG`~GCzS1E*>_(h>2gx&A2 zOg|R&J^O(Rd2Z0?ah}T#veToa)J^H27JArI!ZS3J?Q#9%4!Wu@SzmL2qrnABrs*v7 z7>vtifDSEVz5A)>Ap5)?4`X~8nZM5NQnEJV^Cfx{fI<9ELYCcIsR_2D? zf?@R6VcjP{D``^ICZm4eECax9%RR6fHEEa{+$@%JYT_}0G!R-k?;PGvSl!_6WeWZ* zfu0a2jV;qkPeGZU>+yW2vD)fRci7nAZmR~i>BuTb9b%{ttmWPF248>)3w#=%3FH?@ zrSRC|yQZQ=Y|LJdgIExG*be!eDq|x^hOAKccO}(2pkl zg6}TE3y&q<7muaHY{#3-RvZMepTIYA2f07~x;m0Iu4cTMn$OD|XIXt~_%uK{kcNGf zqCK!se2(3^U`Q%1;xL&@FaJU(&0H(-i4A;ewGvv#Koj6p=kl8IT)h1hC6DMGO1)2bVgYRomn(H5es2MnAE()@_9v`le-zt|XU9hQB0-L{vyED1;z>!~h5 zR->?Mn^rV|X8=Y?^s9BV_%(hTK5H!hlzl|vCCd3go999w&EmI7uKBp6yP@MTzJd&Y z#IzsA^G_D!p};2Z+|MjtWg1$VQN6p4o?mLG)@2X(3V7GYnZV_*hhHtp?jRosQ>*?6 z-y4Zu9}7v#dF2Gx*)3=fQMAY?62uL?;Tufrm$Jb6{y1@j%l=Ns{C{<3fV{(vBZcu& zdc?y*+R1x;i!Da8BAGr-Dc|ob@dH{=s&4J^MIpBBDtsj*YB^iJ^840;8ol*brC5Z% zlVC*3Z?QjG5QsDUWbNL6vyt#UcjN-#yavw9Ry($QC}$P*)?Kr0>>E}W+$)UZlrmkAAq;%ryzY*#T@D=XmB|H)WB0kn8rnCzQ@ zt(tb8x#L`cT*C9-^0TqRw@NvgKD+;Jt`o%rqIvFXr(hq)e@4TSb0)^vISdAlV0QO!b zHTs;h$hr}8AKry$CPJ~WD7H=8kK?3FdsS0ypN3)+zwlf;P49u^(^;Z&;Y{-{3dhMv z@=RgexBiKVT%qp*BCtu0zTP47KrPC3$r*5V)Uss%=BAJc!t!?O5dVL`Hy7pq0^f+i z8miRG6e0Z3KjG3uRBlFrhw5Cmu3mvP%`!SCtETRCNLRC9t+6ng9f4#&aQT4t8G`1I zt7=1UTUtoKi5?LGvIJvYMoH#NS$WvgYKzXn=|#W?v~9O#WyLWelp4(MHRYWZ!EMq5 z-l#uCRxr4p|Knd^w?KsE%~3-4Aabz^pdxK`oz@qdE4MF!5w!zL>?322cO)^3V{;Em zMEgZ+^r0yW41!J!z!IGdiBgmb_7-B z($kdbA4YYLMknVp6-gFpn^!5_P3|BNZ-SY#21N~EE#)!yzg?O&pU4$@NG5!u1Q=j! z-9GYFg2upq-d$IYG)OzWk;o57zQodjH5Fhvo5#hT(O&%$#QN!N-*>Av4B@CHX#FpH zJrTtQ^@~VfH;O8!e*mmM1K~0$D2%;QrtT?*XB@2SVn z#sAu8HXTlxw9O^BHZxPGOJGOG!2*5AcR}l5(SM*lB`p~Y{m}LI#aaZ0;I`WErwioe zQRBt-<-tN)NUX!wl`qwg>7>kBHH~Zy;I%)@i8 zZ~4m&WNr|Xh< zTySgZVDi%OuRMjpyjELXJlNpd8A8NYCZ0s9sVLv%@^&El;>$OSZcKxN52ME7U2Whk zL7q-aNQmy)QSOS@0nFKGJ!uQtIIj&^OY81OsQNIyiyC9+ygf0{uc%;HGi*K_p@Z*n z@+1vZ53Q#$)$=^|iq70u&(~LWMo!!6U?x5ZEt_*Vn{*+IuS4in)Sp%!UEi#HQ#<6B zzunNCYAQKfvn>VMjdC9ZAQn5!su_dD!o0sZ!6|ZW9}P& zymgd_e^64sn!A3Os(L?X@AHv#e$^&EYF+VjuO1}Jsxq3@ZxYYmp73?c~EBgT4Lup}}~JX#%;Zb3(B zbiql%*ScO&$}XN5|VaOl=nGq$x}r5l`{Kev#w%f3d}$B^+O1w`wuPXFJ5d z;x;L^vjMkvsGrHldi$+`4h?dJD}#%Yd7^xBbyEV<21zro`vv7ypSP&0H+=wft~*31 z3OX3u$35eLr$9#8xu@V*W7*=qcpR`h1P1U~-A!VR6^oJN{`09 zGY_7&OVKpkLwyYA6D)WxQtfvKr2)YtS=9L^D|n|EHKE?p#TJGTGs;cWJ%Pxn*Nz7e zcd)ycw;A%GT5e)?au|gjtOIH26vIn=ruW?`0#H22kg3cPS|A?s}x>AJN9GOrHag2Q$goT;vEXkakHrQ{1`JIpQx?Ez*i|JT@f z8h1`P$|687<`>&N6*(=he9n7~`QGw%HqLuDwSG4({DavSX_#PTnq-J#e>Jv4R(iZ| zxZdRapi?OM+A~Xgs>lkn5tW7XMfT&G8;BQpk0v#|2W>RGxdMR@51h66`@Lv`S9kwn z1~njZBN-|D3T!+iI zwByiixx(VCY@@UL@^(Q&O0+@`{%Ut5Wk7IpPU8>I(ma^T)Al?$W6V?(kSX79NpwGJ z59+FYHCgXEySFYPG4S&r&}7jf>Wli$&N$=jITrbrRE<9AK5Y_X+yD-J0AMNrsaC0b z|NB@_NM*(JNKJHOt(sF7?Or*7619+TBO5=_-|r+`VI_y%P67f5Enw;{*wz^Y0%Vgy z$cf(kl>zhbL;nQW`i@ix5SBTqj4Q8d z;7aX#v<<+C#1|rOk!gjH1ixW>ht`=JrR01-3b@l1;FvHVqHj@?1PRi)HR}JmRH$&^ zeo{qbVwwuD?=Z>3ssNh)HdwIr^4qf+YPI4t+{r}&#p&|Ehi>U|Jn7H)Eq`SZ8h} z&>m9DQA4LuUVi8AeyQqa@a*55T$BWUuNO|DTIsKqrU3V5BD1xe$ILbE101()?D3wG zdne#EezwL^wQ;eIlC$rM z#~9DVgOaa=3Yo6UdTgICt>8mj07R(La0H zpl`U?O}>oxynoK}*|__CIt&Y046Xp{pKg_Z3T&9#Sr>hUwJdqJG%|B*_cC*u?^!VO zaUTDD(Ez-FH=-!;t$mF$him*t?BT7{?J}C;+r!$J0_dqx{H|{GYwCkzrQ6nO9Z$?? zO~3hcY?VBTt)jcsLDD)-6`xJ>AxS65X2v-hYlK~@Syf)}l4NGEaZyR`7JoFqib`7m z8zx%)4F2pg!sdJ-M0$V+0fk1%_+8}JEG?{?$a18BjgWf9)=cJQ5B0s-TW{Ac2cQE_ zrmC9{UPRyhld#@PP+kvizy~rcna5(*s`F|>fg-{bCkGVxhmM1pUoUuBh&mZqcYdt% zOUn!E&^}gGB^KS-r$`)AzTE6#ueCf+kA2FfQr{mCqWS@YPuPGkEGoa}o}W39oLWO#hPn{A=s>saFk4~%1D&GMjY z%!S$97;BcaNf1c{P`Xc%+Ne;e!{?rmuy4*f+_*nyM|!+yXo)30nazXDgr}{jd}C?LF=_e%1y>h_-GH0X*&yj z6~d>*M;VaBI}MMB-ZcD8E)y%N3o%M){u1<0w2!G&H%I#K#;)Qr8M)-{Ue~qk3%t6+ z3Fj^k{fqSz03WjQUq*5Pym!+1YE$(F0m_~;46(m_f@3FpgE0?j%+%xfT5aCdV4!h1 zna|%qXMpzLVd!?~yy2PJ*xB?_pj)%xE^zCfvi%S8z5&4A$t$^h>b$?mq(N%J)m6#T zMecx9-TgYS^LY1`nY?2FZ`$$oocyRpn~1PlmrDPH@x`t!LZvCUwW{JiT1g84jREY46uV8y^VYy^NN5SccZ#bDEx0ZpAQy zym@`E5D8g~-v6^7i(BKjm)BHe9PPgHMD1;I;C!~F^-_Fduciery|O0I@_%rRMH$Q0 z%H+D0z>`9(c%~dHoxR3CCGu^P{-5vlq5Km!dAi=`eE3Z4tDnIC1&`#z|EHgISg^+J zTx^i+s_zZ!k16S|eVKeT>XcEQ4QNA4JaFp2wR3CJ3b`tu!-s+jOW)ke{kW+0S9-=t z-d7HS`va#cFN#(^#CmyY%oFXmb0>T_ukvr<+E*t+RR5K|tu?LqVZQT4fA$%3IXUO! z4$t1mTFK3Ox*}m-(hCdGYtF|jPfGZ2lQS2Su<8R1ZFq#2%;f@Z#|Abn7=pwnWiNj* zy>#zCxJcR>i?n^O`WF`Vik*mAxBg>2mc=X>$0V_8H=Nm+ z;QA_JgOcd^y`P?yo}O2?_`mJdZD%`=?~1RDz5V!H*z>th|DK$FK8zPQ3TW_-H%0SL;`cuM*b?q(sY$e7nQHBKR>97AY ZGH&nx=Y8tF-)v1yZ%=p6>7L&?bF4L0QBX+%022UE?feV`S*pr;0RZ^tL{