diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..929b6bb4f5a33f37a97181698c5a0626021018f0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +* text eol=lf + +*.py text +*.json text +*.sh text + +*.png binary +*.h5 binary +*.jpg binary +*.bin binary diff --git a/.gitignore b/.gitignore index 7f249738c56e97da80aaecaebb99c528eba78d3a..60c6519f7724a7ca08cac3263b595400dba9fdd2 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,8 @@ **/.project **/.pydevproject **/.settings/org.eclipse.core.resources.prefs +tangostationcontrol/dist +tangostationcontrol/build +**/.ipynb_checkpoints +**/pending_log_messages.db +**/.eggs diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8dc2303883c44a3a4ad40e059a734378c6887e6f..27593ca877e544634c42b303a1dc3d756df4cb41 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,35 +2,96 @@ # images is in place. image: artefact.skao.int/ska-tango-images-tango-itango:9.3.5 variables: + GIT_SUBMODULE_STRATEGY: recursive PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" - # The PBR dependency requires a set version, not actually used - # Instead `util/lofar_git.py:get_version()` is used. - PBR_VERSION: "0.1" cache: paths: - .cache/pip - - devices/.tox stages: - building - linting - static-analysis - unit-tests -linting: + - integration-tests + - packaging +newline_at_eof: stage: linting + before_script: + - pip3 install -r tangostationcontrol/test-requirements.txt + script: +# TODO(Corne): Ignore shell files in submodules more cleanly + - flake8 --filename *.sh,*.conf,*.md,*.yml --select=W292 --exclude docker-compose/tango-prometheus-exporter,.tox,.egg-info,docker +python_linting: + stage: linting + before_script: + - sudo apt-get update + - sudo apt-get install -y git script: - - cd devices + - cd tangostationcontrol - tox -e pep8 -static-analysis: +bandit: stage: static-analysis - allow_failure: true + before_script: + - sudo apt-get update + - sudo apt-get install -y git script: - - cd devices + - cd tangostationcontrol - tox -e bandit +shellcheck: + stage: static-analysis + before_script: + - sudo apt-get update + - sudo apt-get install -y shellcheck + script: +# TODO(Corne): Ignore shell files in submodules + - shellcheck **/*.sh unit_test: stage: unit-tests before_script: - sudo apt-get update - sudo apt-get install -y git script: - - cd devices + - cd tangostationcontrol - tox -e py37 +integration_test_docker: + stage: integration-tests + image: docker:latest + tags: + - privileged + services: + - name: docker:dind + variables: + DOCKER_TLS_CERTDIR: "/certs" + before_script: + - apk add --update make bash docker-compose + - apk add --update bind-tools + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + script: + - touch /root/.Xauthority +# Hack BASH_SOURCE into sourced files, docker its sh shell won't set this + - export BASH_SOURCE=$(pwd)/bootstrap/etc/lofar20rc.sh +# Hack HOSTNAME env variable into host.docker.internal, set in docker-compose + - export HOSTNAME=host.docker.internal +# - export HOSTNAME=$(hostname -i) +# - export HOSTNAME=$(cat /run/systemd/netif/leases/2 | grep ^ADDRESS= | awk -F'=' '{print $2}') +# source the lofarrc file and mask its non zero exit code + - . bootstrap/etc/lofar20rc.sh || true +# TANGO_HOST must be unset our databaseds will be unreachable + - unset TANGO_HOST +# Allow integration test to execute + - chmod u+x $CI_PROJECT_DIR/sbin/run_integration_test.sh +# Do not remove 'bash' or statement will be ignored by primitive docker shell + - bash $CI_PROJECT_DIR/sbin/run_integration_test.sh +wheel_packaging: + stage: packaging + artifacts: + paths: + - tangostationcontrol/dist/*.whl + before_script: + - sudo apt-get update + - sudo apt-get install -y git + - pip3 install -r tangostationcontrol/test-requirements.txt + - pip3 install -r docker-compose/itango/lofar-requirements.txt + script: + - cd tangostationcontrol + - python setup.py bdist_wheel diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..ef820d4039a54bf590e5c675c97a718b0681dc6e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "docker-compose/tango-prometheus-exporter/ska-tango-grafana-exporter"] + path = docker-compose/tango-prometheus-exporter/ska-tango-grafana-exporter + url = https://git.astron.nl/lofar2.0/ska-tango-grafana-exporter.git + branch = station-control diff --git a/CDB/LOFAR_ConfigDb.json b/CDB/LOFAR_ConfigDb.json index 705f701556224fa4936e35916993aa2d4d05107e..6886437ce7cf02ee741b5b70f07ebfbf71b08b4d 100644 --- a/CDB/LOFAR_ConfigDb.json +++ b/CDB/LOFAR_ConfigDb.json @@ -1,797 +1,869 @@ -{ - "servers": { - "Femto": { - "CS999": { - "Femto": { - "opc-ua/test-femto/1": {} - } - } - }, - "observation_control": { - "LTS": { - "ObservationControl": { - "LTS/ObservationControl/1": {} - } - } - }, - "PCC": { - "LTS": { - "PCC": { - "LTS/PCC/1": { - "attribute_properties": { - "Ant_mask_RW": { - "archive_period": [ - "600000" - ] - }, - "CLK_Enable_PWR_R": { - "archive_period": [ - "600000" - ] - }, - "CLK_I2C_STATUS_R": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "CLK_PLL_error_R": { - "archive_period": [ - "600000" - ] - }, - "CLK_PLL_locked_R": { - "archive_period": [ - "600000" - ] - }, - "CLK_monitor_rate_RW": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "CLK_translator_busy_R": { - "archive_period": [ - "600000" - ] - }, - "HBA_element_LNA_pwr_R": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "HBA_element_LNA_pwr_RW": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "HBA_element_beamformer_delays_R": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "HBA_element_beamformer_delays_RW": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "HBA_element_led_R": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "HBA_element_led_RW": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "HBA_element_pwr_R": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "HBA_element_pwr_RW": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "RCU_ADC_lock_R": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "RCU_I2C_STATUS_R": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "RCU_ID_R": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "RCU_LED0_R": { - "archive_period": [ - "600000" - ] - }, - "RCU_LED0_RW": { - "archive_period": [ - "600000" - ] - }, - "RCU_LED1_R": { - "archive_period": [ - "600000" - ] - }, - "RCU_LED1_RW": { - "archive_period": [ - "600000" - ] - }, - "RCU_Pwr_dig_R": { - "archive_period": [ - "600000" - ] - }, - "RCU_attenuator_R": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "RCU_attenuator_RW": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "RCU_band_R": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "RCU_band_RW": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "RCU_mask_RW": { - "archive_period": [ - "600000" - ] - }, - "RCU_monitor_rate_RW": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1.0", - "1.0" - ], - "rel_change": [ - "-1.0", - "1.0" - ] - }, - "RCU_temperature_R": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1.0", - "1.0" - ], - "rel_change": [ - "-1.0", - "1.0" - ] - }, - "RCU_translator_busy_R": { - "archive_period": [ - "600000" - ] - }, - "RCU_version_R": { - "archive_period": [ - "600000" - ] - }, - "State": { - "archive_period": [ - "600000" - ], - "event_period": [ - "0" - ] - }, - "Status": { - "archive_period": [ - "600000" - ], - "event_period": [ - "0" - ] - } - }, - "properties": { - "OPC_Server_Name": [ - "ltspi.astron.nl" - ], - "OPC_Server_Port": [ - "4842" - ], - "OPC_Time_Out": [ - "5.0" - ], - "polled_attr": [ - "state", - "1000", - "status", - "1000", - "ant_mask_rw", - "1000", - "rcu_adc_lock_r", - "1000", - "rcu_attenuator_r", - "1000", - "rcu_attenuator_rw", - "1000", - "rcu_band_r", - "1000", - "rcu_band_rw", - "1000", - "rcu_id_r", - "1000", - "rcu_led0_r", - "1000", - "rcu_led0_rw", - "1000", - "rcu_mask_rw", - "1000", - "rcu_monitor_rate_rw", - "1000", - "rcu_pwr_dig_r", - "1000", - "rcu_temperature_r", - "1000", - "rcu_version_r", - "1000", - "hba_element_beamformer_delays_r", - "1000", - "hba_element_beamformer_delays_rw", - "1000", - "hba_element_led_r", - "1000", - "hba_element_led_rw", - "1000", - "hba_element_pwr_r", - "1000", - "hba_element_pwr_rw", - "1000", - "clk_enable_pwr_r", - "1000", - "clk_i2c_status_r", - "1000", - "clk_monitor_rate_rw", - "1000", - "clk_pll_error_r", - "1000", - "clk_pll_locked_r", - "1000", - "clk_translator_busy_r", - "1000", - "hba_element_lna_pwr_r", - "1000", - "hba_element_lna_pwr_rw", - "1000", - "rcu_i2c_status_r", - "1000", - "rcu_led1_r", - "1000", - "rcu_led1_rw", - "1000", - "rcu_translator_busy_r", - "1000" - ] - } - } - } - } - }, - "random_data": { - "LTS": { - "Random_Data": { - "LTS/random_data/1": { - "properties": { - "polled_attr": [ - "rnd1", - "1000", - "rnd2", - "1000", - "rnd3", - "1000", - "rnd4", - "1000", - "rnd5", - "1000", - "rnd6", - "1000", - "rnd7", - "1000", - "rnd8", - "1000", - "rnd9", - "1000", - "rnd10", - "1000", - "rnd11", - "1000", - "rnd12", - "1000", - "rnd13", - "1000", - "rnd14", - "1000", - "rnd15", - "1000", - "rnd16", - "1000", - "rnd17", - "1000", - "rnd18", - "1000", - "rnd19", - "1000", - "rnd20", - "1000", - "rnd21", - "1000", - "state", - "1000", - "status", - "1000" - ] - } - }, - "LTS/random_data/2": { - "properties": { - "polled_attr": [ - "rnd1", - "100", - "rnd2", - "100", - "rnd3", - "100", - "rnd4", - "100", - "rnd5", - "100", - "rnd6", - "100", - "rnd7", - "100", - "rnd8", - "100", - "rnd9", - "100", - "rnd10", - "100", - "rnd11", - "100", - "rnd12", - "100", - "rnd13", - "100", - "rnd14", - "100", - "rnd15", - "100", - "rnd16", - "100", - "rnd17", - "100", - "rnd18", - "100", - "rnd19", - "100", - "rnd20", - "100" - ] - } - } - } - } - }, - "SDP": { - "LTS": { - "SDP": { - "LTS/SDP/1": { - "attribute_properties": { - "SDP_mask_RW": { - "event_period": [ - "60000" - ] - }, - "State": { - "archive_period": [ - "600000" - ] - }, - "Status": { - "archive_period": [ - "600000" - ] - }, - "fpga_mask_RW": { - "archive_period": [ - "600000" - ] - }, - "fpga_scrap_R": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "fpga_scrap_RW": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "fpga_status_R": { - "archive_period": [ - "600000" - ] - }, - "fpga_temp_R": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "fpga_version_R": { - "archive_period": [ - "600000" - ] - }, - "fpga_weights_R": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "fpga_weights_RW": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "tr_busy_R": { - "archive_period": [ - "600000" - ] - }, - "tr_reload_RW": { - "archive_period": [ - "600000" - ] - }, - "tr_tod_R": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-1", - "1" - ], - "rel_change": [ - "-1", - "1" - ] - }, - "tr_uptime_R": { - "archive_period": [ - "600000" - ], - "archive_rel_change": [ - "-3600", - "3600" - ], - "rel_change": [ - "-10", - "10" - ] - } - }, - "properties": { - "OPC_Server_Name": [ - "dop36.astron.nl" - ], - "OPC_Server_Port": [ - "4840" - ], - "OPC_Time_Out": [ - "5.0" - ], - "polled_attr": [ - "fpga_temp_r", - "1000", - "state", - "1000", - "status", - "1000", - "fpga_mask_rw", - "1000", - "fpga_scrap_r", - "1000", - "fpga_scrap_rw", - "1000", - "fpga_status_r", - "1000", - "fpga_version_r", - "1000", - "fpga_weights_r", - "1000", - "fpga_weights_rw", - "1000", - "tr_busy_r", - "1000", - "tr_reload_rw", - "1000", - "tr_tod_r", - "1000", - "tr_uptime_r", - "1000" - ] - } - } - } - } - }, - "SST": { - "LTS": { - "SST": { - "LTS/SST/1": { - "properties": { - "Statistics_Client_Port": [ - "5001" - ], - "OPC_Server_Name": [ - "dop36.astron.nl" - ], - "OPC_Server_Port": [ - "4840" - ], - "OPC_Time_Out": [ - "5.0" - ] - } - } - } - } - }, - "StatsCrosslet": { - "CS997": { - "StatsCrosslet": { - "opc-ua/test-crossletstats/1": { - "attribute_properties": { - "visibilities_imag": { - "archive_rel_change": [ - "-0.1", - "0.1" - ], - "rel_change": [ - "-0.1", - "0.1" - ] - }, - "visibilities_real": { - "archive_rel_change": [ - "-0.1", - "0.1" - ], - "rel_change": [ - "-0.1", - "0.1" - ] - } - }, - "properties": { - "polled_attr": [ - "integration_time", - "0", - "pause_time", - "0", - "rcu_modes", - "0", - "state", - "0", - "status", - "0", - "subband", - "0", - "time_stamp", - "0", - "visibilities_imag", - "0", - "visibilities_real", - "0" - ] - } - } - } - } - } - } -} +{ + "servers": { + "Docker": { + "STAT": { + "Docker": { + "STAT/Docker/1": {} + } + } + }, + "Femto": { + "CS999": { + "Femto": { + "opc-ua/test-femto/1": {} + } + } + }, + "ObservationControl": { + "STAT": { + "ObservationControl": { + "STAT/ObservationControl/1": {} + } + } + }, + "boot": { + "STAT": { + "Boot": { + "STAT/Boot/1": {} + } + } + }, + "APSCT": { + "STAT": { + "APSCT": { + "STAT/APSCT/1": { + "properties": { + } + } + } + } + }, + "APSPU": { + "STAT": { + "APSPU": { + "STAT/APSPU/1": { + "properties": { + } + } + } + } + }, + "RECV": { + "STAT": { + "RECV": { + "STAT/RECV/1": { + "attribute_properties": { + "Ant_mask_RW": { + "archive_period": [ + "600000" + ] + }, + "CLK_Enable_PWR_R": { + "archive_period": [ + "600000" + ] + }, + "CLK_I2C_STATUS_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "CLK_PLL_error_R": { + "archive_period": [ + "600000" + ] + }, + "CLK_PLL_locked_R": { + "archive_period": [ + "600000" + ] + }, + "CLK_monitor_rate_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "CLK_translator_busy_R": { + "archive_period": [ + "600000" + ] + }, + "HBA_element_LNA_pwr_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "HBA_element_LNA_pwr_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "HBA_element_beamformer_delays_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "HBA_element_beamformer_delays_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "HBA_element_led_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "HBA_element_led_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "HBA_element_pwr_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "HBA_element_pwr_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "RCU_ADC_lock_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "RCU_I2C_STATUS_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "RCU_ID_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "RCU_LED0_R": { + "archive_period": [ + "600000" + ] + }, + "RCU_LED0_RW": { + "archive_period": [ + "600000" + ] + }, + "RCU_LED1_R": { + "archive_period": [ + "600000" + ] + }, + "RCU_LED1_RW": { + "archive_period": [ + "600000" + ] + }, + "RCU_Pwr_dig_R": { + "archive_period": [ + "600000" + ] + }, + "RCU_attenuator_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "RCU_attenuator_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "RCU_band_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "RCU_band_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "RCU_mask_RW": { + "archive_period": [ + "600000" + ] + }, + "RCU_monitor_rate_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1.0", + "1.0" + ], + "rel_change": [ + "-1.0", + "1.0" + ] + }, + "RCU_temperature_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1.0", + "1.0" + ], + "rel_change": [ + "-1.0", + "1.0" + ] + }, + "RCU_translator_busy_R": { + "archive_period": [ + "600000" + ] + }, + "RCU_version_R": { + "archive_period": [ + "600000" + ] + }, + "State": { + "archive_period": [ + "600000" + ], + "event_period": [ + "0" + ] + }, + "Status": { + "archive_period": [ + "600000" + ], + "event_period": [ + "0" + ] + } + }, + "properties": { + "polled_attr": [ + "state", + "1000", + "status", + "1000", + "ant_mask_rw", + "1000", + "rcu_adc_lock_r", + "1000", + "rcu_attenuator_r", + "1000", + "rcu_attenuator_rw", + "1000", + "rcu_band_r", + "1000", + "rcu_band_rw", + "1000", + "rcu_id_r", + "1000", + "rcu_led0_r", + "1000", + "rcu_led0_rw", + "1000", + "rcu_mask_rw", + "1000", + "rcu_monitor_rate_rw", + "1000", + "rcu_pwr_dig_r", + "1000", + "rcu_temperature_r", + "1000", + "rcu_version_r", + "1000", + "hba_element_beamformer_delays_r", + "1000", + "hba_element_beamformer_delays_rw", + "1000", + "hba_element_led_r", + "1000", + "hba_element_led_rw", + "1000", + "hba_element_pwr_r", + "1000", + "hba_element_pwr_rw", + "1000", + "clk_enable_pwr_r", + "1000", + "clk_i2c_status_r", + "1000", + "clk_monitor_rate_rw", + "1000", + "clk_pll_error_r", + "1000", + "clk_pll_locked_r", + "1000", + "clk_translator_busy_r", + "1000", + "hba_element_lna_pwr_r", + "1000", + "hba_element_lna_pwr_rw", + "1000", + "rcu_i2c_status_r", + "1000", + "rcu_led1_r", + "1000", + "rcu_led1_rw", + "1000", + "rcu_translator_busy_r", + "1000" + ] + } + } + } + } + }, + "random_data": { + "STAT": { + "Random_Data": { + "STAT/random_data/1": { + "properties": { + "polled_attr": [ + "rnd1", + "1000", + "rnd2", + "1000", + "rnd3", + "1000", + "rnd4", + "1000", + "rnd5", + "1000", + "rnd6", + "1000", + "rnd7", + "1000", + "rnd8", + "1000", + "rnd9", + "1000", + "rnd10", + "1000", + "rnd11", + "1000", + "rnd12", + "1000", + "rnd13", + "1000", + "rnd14", + "1000", + "rnd15", + "1000", + "rnd16", + "1000", + "rnd17", + "1000", + "rnd18", + "1000", + "rnd19", + "1000", + "rnd20", + "1000", + "rnd21", + "1000", + "state", + "1000", + "status", + "1000" + ] + } + }, + "STAT/random_data/2": { + "properties": { + "polled_attr": [ + "rnd1", + "100", + "rnd2", + "100", + "rnd3", + "100", + "rnd4", + "100", + "rnd5", + "100", + "rnd6", + "100", + "rnd7", + "100", + "rnd8", + "100", + "rnd9", + "100", + "rnd10", + "100", + "rnd11", + "100", + "rnd12", + "100", + "rnd13", + "100", + "rnd14", + "100", + "rnd15", + "100", + "rnd16", + "100", + "rnd17", + "100", + "rnd18", + "100", + "rnd19", + "100", + "rnd20", + "100" + ] + } + } + } + } + }, + "SDP": { + "STAT": { + "SDP": { + "STAT/SDP/1": { + "attribute_properties": { + "SDP_mask_RW": { + "event_period": [ + "60000" + ] + }, + "State": { + "archive_period": [ + "600000" + ] + }, + "Status": { + "archive_period": [ + "600000" + ] + }, + "fpga_mask_RW": { + "archive_period": [ + "600000" + ] + }, + "fpga_scrap_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "fpga_scrap_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "fpga_status_R": { + "archive_period": [ + "600000" + ] + }, + "fpga_temp_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "fpga_version_R": { + "archive_period": [ + "600000" + ] + }, + "fpga_weights_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "fpga_weights_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "tr_busy_R": { + "archive_period": [ + "600000" + ] + }, + "tr_reload_RW": { + "archive_period": [ + "600000" + ] + }, + "tr_tod_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "tr_uptime_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-3600", + "3600" + ], + "rel_change": [ + "-10", + "10" + ] + } + }, + "properties": { + "polled_attr": [ + "fpga_temp_r", + "1000", + "state", + "1000", + "status", + "1000", + "fpga_mask_rw", + "1000", + "fpga_scrap_r", + "1000", + "fpga_scrap_rw", + "1000", + "fpga_status_r", + "1000", + "fpga_version_r", + "1000", + "fpga_weights_r", + "1000", + "fpga_weights_rw", + "1000", + "tr_busy_r", + "1000", + "tr_reload_rw", + "1000", + "tr_tod_r", + "1000", + "tr_uptime_r", + "1000" + ] + } + } + } + } + }, + "SST": { + "STAT": { + "SST": { + "STAT/SST/1": { + "properties": { + "Statistics_Client_UDP_Port": [ + "5001" + ], + "Statistics_Client_TCP_Port": [ + "5101" + ], + "FPGA_sst_offload_hdr_udp_destination_port_RW_default": [ + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001" + ] + } + } + } + } + }, + "XST": { + "STAT": { + "XST": { + "STAT/XST/1": { + "properties": { + "Statistics_Client_UDP_Port": [ + "5002" + ], + "Statistics_Client_TCP_Port": [ + "5102" + ], + "FPGA_xst_offload_hdr_udp_destination_port_RW_default": [ + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002" + ] + } + } + } + } + }, + "UNB2": { + "STAT": { + "UNB2": { + "STAT/UNB2/1": { + "properties": { + } + } + } + } + }, + "StatsCrosslet": { + "CS997": { + "StatsCrosslet": { + "opc-ua/test-crossletstats/1": { + "attribute_properties": { + "visibilities_imag": { + "archive_rel_change": [ + "-0.1", + "0.1" + ], + "rel_change": [ + "-0.1", + "0.1" + ] + }, + "visibilities_real": { + "archive_rel_change": [ + "-0.1", + "0.1" + ], + "rel_change": [ + "-0.1", + "0.1" + ] + } + }, + "properties": { + "polled_attr": [ + "integration_time", + "0", + "pause_time", + "0", + "rcu_modes", + "0", + "state", + "0", + "status", + "0", + "subband", + "0", + "time_stamp", + "0", + "visibilities_imag", + "0", + "visibilities_real", + "0" + ] + } + } + } + } + } + } +} diff --git a/CDB/MAXIV-ConfigDb.json b/CDB/MAXIV-ConfigDb.json index 5c4c63a977632948cb6351169902d4654fe376ae..2087ec3feb67a7ed362fd415a9ab4c654d6741f6 100644 --- a/CDB/MAXIV-ConfigDb.json +++ b/CDB/MAXIV-ConfigDb.json @@ -1,24 +1,24 @@ -{ - "servers": { - "Femto": { - "TangoRestServer": { - "tango-rest": { - "TangoRestServer": { - "tango-rest/rest/0": { - "properties": { - "TANGO_DB": [ - "tango://databaseds:10000/sys/database/2" - ], - "TOMCAT_AUTH_METHOD": [ - "plain" - ], - "TOMCAT_PORT": [ - "8080" - ] - } - } - } - } - } - } -} +{ + "servers": { + "Femto": { + "TangoRestServer": { + "tango-rest": { + "TangoRestServer": { + "tango-rest/rest/0": { + "properties": { + "TANGO_DB": [ + "tango://databaseds:10000/sys/database/2" + ], + "TOMCAT_AUTH_METHOD": [ + "plain" + ], + "TOMCAT_PORT": [ + "8080" + ] + } + } + } + } + } + } +} diff --git a/CDB/default_ConfigDb.json b/CDB/default_ConfigDb.json index 52e0cb91f9c04a2a6fd732ae859c5c619a5e8c7b..a33ece273af53c2a3934b49b4d1d09b539ae0dc8 100644 --- a/CDB/default_ConfigDb.json +++ b/CDB/default_ConfigDb.json @@ -1,80 +1,80 @@ -{ - "servers": { - "DataBaseds": { - "2": { - "DataBase": { - "sys/database/2": {} - } - } - }, - "TangoAccessControl": { - "1": { - "TangoAccessControl": { - "sys/access_control/1": {} - } - } - }, - "TangoRestServer": { - "rest": { - "TangoRestServer": { - "sys/rest/0": {} - } - } - }, - "TangoTest": { - "test": { - "TangoTest": { - "sys/tg_test/1": {} - } - } - }, - "hdbppcm-srv": { - "01": { - "HdbConfigurationManager": { - "archiving/hdbpp/confmanager01": { - "properties": { - "ArchiverList": [ - "archiving/hdbpp/eventsubscriber01" - ], - "LibConfiguration": [ - "host=archiver-maria-db", - "libname=libhdb++mysql.so.6", - "dbname=hdbpp", - "port=3306", - "user=tango", - "password=tango" - ], - "MaxSearchSize": [ - "1000" - ] - } - } - } - } - }, - "hdbppes-srv": { - "01": { - "HdbEventSubscriber": { - "archiving/hdbpp/eventsubscriber01": { - "properties": { - "CheckPeriodicTimeoutDelay": [ - "5" - ], - "LibConfiguration": [ - "host=archiver-maria-db", - "libname=libhdb++mysql.so.6", - "dbname=hdbpp", - "port=3306", - "user=tango", - "password=tango" - ], - "PollingThreadPeriod": [ - "3" - ] - } - } - } - } - } - } -} +{ + "servers": { + "DataBaseds": { + "2": { + "DataBase": { + "sys/database/2": {} + } + } + }, + "TangoAccessControl": { + "1": { + "TangoAccessControl": { + "sys/access_control/1": {} + } + } + }, + "TangoRestServer": { + "rest": { + "TangoRestServer": { + "sys/rest/0": {} + } + } + }, + "TangoTest": { + "test": { + "TangoTest": { + "sys/tg_test/1": {} + } + } + }, + "hdbppcm-srv": { + "01": { + "HdbConfigurationManager": { + "archiving/hdbpp/confmanager01": { + "properties": { + "ArchiverList": [ + "archiving/hdbpp/eventsubscriber01" + ], + "LibConfiguration": [ + "host=archiver-maria-db", + "libname=libhdb++mysql.so.6", + "dbname=hdbpp", + "port=3306", + "user=tango", + "password=tango" + ], + "MaxSearchSize": [ + "1000" + ] + } + } + } + } + }, + "hdbppes-srv": { + "01": { + "HdbEventSubscriber": { + "archiving/hdbpp/eventsubscriber01": { + "properties": { + "CheckPeriodicTimeoutDelay": [ + "5" + ], + "LibConfiguration": [ + "host=archiver-maria-db", + "libname=libhdb++mysql.so.6", + "dbname=hdbpp", + "port=3306", + "user=tango", + "password=tango" + ], + "PollingThreadPeriod": [ + "3" + ] + } + } + } + } + } + } +} diff --git a/CDB/integration_ConfigDb.json b/CDB/integration_ConfigDb.json new file mode 100644 index 0000000000000000000000000000000000000000..7cfbd82751791d7968315923edfc2ba971480308 --- /dev/null +++ b/CDB/integration_ConfigDb.json @@ -0,0 +1,305 @@ +{ + "servers": { + "APSCT": { + "STAT": { + "APSCT": { + "STAT/APSCT/1": { + "properties": { + "OPC_Server_Name": [ + "apsct-sim" + ], + "OPC_Server_Port": [ + "4843" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "APSPU": { + "STAT": { + "APSPU": { + "STAT/APSPU/1": { + "properties": { + "OPC_Server_Name": [ + "apspu-sim" + ], + "OPC_Server_Port": [ + "4843" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "RECV": { + "STAT": { + "RECV": { + "STAT/RECV/1": { + "properties": { + "OPC_Server_Name": [ + "recv-sim" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "SDP": { + "STAT": { + "SDP": { + "STAT/SDP/1": { + "properties": { + "OPC_Server_Name": [ + "sdptr-sim" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_sdp_info_station_id_RW_default": [ + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901" + ], + "polled_attr": [ + "fpga_temp_r", + "1000", + "state", + "1000", + "status", + "1000", + "fpga_mask_rw", + "1000", + "fpga_scrap_r", + "1000", + "fpga_scrap_rw", + "1000", + "fpga_status_r", + "1000", + "fpga_version_r", + "1000", + "fpga_weights_r", + "1000", + "fpga_weights_rw", + "1000", + "tr_busy_r", + "1000", + "tr_reload_rw", + "1000", + "tr_tod_r", + "1000", + "tr_uptime_r", + "1000" + ] + } + } + } + } + }, + "SST": { + "STAT": { + "SST": { + "STAT/SST/1": { + "properties": { + "Statistics_Client_UDP_Port": [ + "5001" + ], + "Statistics_Client_TCP_Port": [ + "5101" + ], + "OPC_Server_Name": [ + "sdptr-sim" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [ + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd" + ], + "FPGA_sst_offload_hdr_ip_destination_address_RW_default": [ + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250" + ], + "FPGA_sst_offload_hdr_udp_destination_port_RW_default": [ + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001" + ] + } + } + } + } + }, + "XST": { + "STAT": { + "XST": { + "STAT/XST/1": { + "properties": { + "Statistics_Client_UDP_Port": [ + "5002" + ], + "Statistics_Client_TCP_Port": [ + "5102" + ], + "OPC_Server_Name": [ + "sdptr-sim" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [ + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd" + ], + "FPGA_xst_offload_hdr_ip_destination_address_RW_default": [ + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250" + ], + "FPGA_xst_offload_hdr_udp_destination_port_RW_default": [ + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002" + ] + } + } + } + } + }, + "UNB2": { + "STAT": { + "UNB2": { + "STAT/UNB2/1": { + "properties": { + "OPC_Server_Name": [ + "unb2-sim" + ], + "OPC_Server_Port": [ + "4841" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + } + } +} diff --git a/CDB/jasper_ConfigDb.json b/CDB/jasper_ConfigDb.json new file mode 100644 index 0000000000000000000000000000000000000000..a156f34ce1e642da3637622439e1cc8f90b7bb4e --- /dev/null +++ b/CDB/jasper_ConfigDb.json @@ -0,0 +1,816 @@ +{ + "servers": { + "Femto": { + "CS999": { + "Femto": { + "opc-ua/test-femto/1": {} + } + } + }, + "observation_control": { + "STAT": { + "ObservationControl": { + "STAT/ObservationControl/1": {} + } + } + }, + "RECV": { + "STAT": { + "RECV": { + "STAT/RECV/1": { + "attribute_properties": { + "Ant_mask_RW": { + "archive_period": [ + "600000" + ] + }, + "CLK_Enable_PWR_R": { + "archive_period": [ + "600000" + ] + }, + "CLK_I2C_STATUS_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "CLK_PLL_error_R": { + "archive_period": [ + "600000" + ] + }, + "CLK_PLL_locked_R": { + "archive_period": [ + "600000" + ] + }, + "CLK_monitor_rate_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "CLK_translator_busy_R": { + "archive_period": [ + "600000" + ] + }, + "HBA_element_LNA_pwr_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "HBA_element_LNA_pwr_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "HBA_element_beamformer_delays_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "HBA_element_beamformer_delays_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "HBA_element_led_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "HBA_element_led_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "HBA_element_pwr_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "HBA_element_pwr_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "RCU_ADC_lock_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "RCU_I2C_STATUS_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "RCU_ID_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "RCU_LED0_R": { + "archive_period": [ + "600000" + ] + }, + "RCU_LED0_RW": { + "archive_period": [ + "600000" + ] + }, + "RCU_LED1_R": { + "archive_period": [ + "600000" + ] + }, + "RCU_LED1_RW": { + "archive_period": [ + "600000" + ] + }, + "RCU_Pwr_dig_R": { + "archive_period": [ + "600000" + ] + }, + "RCU_attenuator_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "RCU_attenuator_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "RCU_band_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "RCU_band_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "RCU_mask_RW": { + "archive_period": [ + "600000" + ] + }, + "RCU_monitor_rate_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1.0", + "1.0" + ], + "rel_change": [ + "-1.0", + "1.0" + ] + }, + "RCU_temperature_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1.0", + "1.0" + ], + "rel_change": [ + "-1.0", + "1.0" + ] + }, + "RCU_translator_busy_R": { + "archive_period": [ + "600000" + ] + }, + "RCU_version_R": { + "archive_period": [ + "600000" + ] + }, + "State": { + "archive_period": [ + "600000" + ], + "event_period": [ + "0" + ] + }, + "Status": { + "archive_period": [ + "600000" + ], + "event_period": [ + "0" + ] + } + }, + "properties": { + "OPC_Server_Name": [ + "ltspi.astron.nl" + ], + "OPC_Server_Port": [ + "4842" + ], + "OPC_Time_Out": [ + "5.0" + ], + "polled_attr": [ + "state", + "1000", + "status", + "1000", + "ant_mask_rw", + "1000", + "rcu_adc_lock_r", + "1000", + "rcu_attenuator_r", + "1000", + "rcu_attenuator_rw", + "1000", + "rcu_band_r", + "1000", + "rcu_band_rw", + "1000", + "rcu_id_r", + "1000", + "rcu_led0_r", + "1000", + "rcu_led0_rw", + "1000", + "rcu_mask_rw", + "1000", + "rcu_monitor_rate_rw", + "1000", + "rcu_pwr_dig_r", + "1000", + "rcu_temperature_r", + "1000", + "rcu_version_r", + "1000", + "hba_element_beamformer_delays_r", + "1000", + "hba_element_beamformer_delays_rw", + "1000", + "hba_element_led_r", + "1000", + "hba_element_led_rw", + "1000", + "hba_element_pwr_r", + "1000", + "hba_element_pwr_rw", + "1000", + "clk_enable_pwr_r", + "1000", + "clk_i2c_status_r", + "1000", + "clk_monitor_rate_rw", + "1000", + "clk_pll_error_r", + "1000", + "clk_pll_locked_r", + "1000", + "clk_translator_busy_r", + "1000", + "hba_element_lna_pwr_r", + "1000", + "hba_element_lna_pwr_rw", + "1000", + "rcu_i2c_status_r", + "1000", + "rcu_led1_r", + "1000", + "rcu_led1_rw", + "1000", + "rcu_translator_busy_r", + "1000" + ] + } + } + } + } + }, + "random_data": { + "STAT": { + "Random_Data": { + "STAT/random_data/1": { + "properties": { + "polled_attr": [ + "rnd1", + "1000", + "rnd2", + "1000", + "rnd3", + "1000", + "rnd4", + "1000", + "rnd5", + "1000", + "rnd6", + "1000", + "rnd7", + "1000", + "rnd8", + "1000", + "rnd9", + "1000", + "rnd10", + "1000", + "rnd11", + "1000", + "rnd12", + "1000", + "rnd13", + "1000", + "rnd14", + "1000", + "rnd15", + "1000", + "rnd16", + "1000", + "rnd17", + "1000", + "rnd18", + "1000", + "rnd19", + "1000", + "rnd20", + "1000", + "rnd21", + "1000", + "state", + "1000", + "status", + "1000" + ] + } + }, + "STAT/random_data/2": { + "properties": { + "polled_attr": [ + "rnd1", + "100", + "rnd2", + "100", + "rnd3", + "100", + "rnd4", + "100", + "rnd5", + "100", + "rnd6", + "100", + "rnd7", + "100", + "rnd8", + "100", + "rnd9", + "100", + "rnd10", + "100", + "rnd11", + "100", + "rnd12", + "100", + "rnd13", + "100", + "rnd14", + "100", + "rnd15", + "100", + "rnd16", + "100", + "rnd17", + "100", + "rnd18", + "100", + "rnd19", + "100", + "rnd20", + "100" + ] + } + } + } + } + }, + "SDP": { + "STAT": { + "SDP": { + "STAT/SDP/1": { + "attribute_properties": { + "SDP_mask_RW": { + "event_period": [ + "60000" + ] + }, + "State": { + "archive_period": [ + "600000" + ] + }, + "Status": { + "archive_period": [ + "600000" + ] + }, + "fpga_mask_RW": { + "archive_period": [ + "600000" + ] + }, + "fpga_scrap_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "fpga_scrap_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "fpga_status_R": { + "archive_period": [ + "600000" + ] + }, + "fpga_temp_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "fpga_version_R": { + "archive_period": [ + "600000" + ] + }, + "fpga_weights_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "fpga_weights_RW": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "tr_busy_R": { + "archive_period": [ + "600000" + ] + }, + "tr_reload_RW": { + "archive_period": [ + "600000" + ] + }, + "tr_tod_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-1", + "1" + ], + "rel_change": [ + "-1", + "1" + ] + }, + "tr_uptime_R": { + "archive_period": [ + "600000" + ], + "archive_rel_change": [ + "-3600", + "3600" + ], + "rel_change": [ + "-10", + "10" + ] + } + }, + "properties": { + "OPC_Server_Name": [ + "dop36.astron.nl" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "polled_attr": [ + "fpga_temp_r", + "1000", + "state", + "1000", + "status", + "1000", + "fpga_mask_rw", + "1000", + "fpga_scrap_r", + "1000", + "fpga_scrap_rw", + "1000", + "fpga_status_r", + "1000", + "fpga_version_r", + "1000", + "fpga_weights_r", + "1000", + "fpga_weights_rw", + "1000", + "tr_busy_r", + "1000", + "tr_reload_rw", + "1000", + "tr_tod_r", + "1000", + "tr_uptime_r", + "1000" + ] + } + } + } + } + }, + "SST": { + "STAT": { + "SST": { + "STAT/SST/1": { + "properties": { + "Statistics_Client_Port": [ + "5001" + ], + "OPC_Server_Name": [ + "dop36.astron.nl" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "UNB2": { + "STAT": { + "UNB2": { + "STAT/UNB2/1": { + "properties": { + "OPC_Server_Name": [ + "despi.astron.nl" + ], + "OPC_Server_Port": [ + "4842" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "StatsCrosslet": { + "CS997": { + "StatsCrosslet": { + "opc-ua/test-crossletstats/1": { + "attribute_properties": { + "visibilities_imag": { + "archive_rel_change": [ + "-0.1", + "0.1" + ], + "rel_change": [ + "-0.1", + "0.1" + ] + }, + "visibilities_real": { + "archive_rel_change": [ + "-0.1", + "0.1" + ], + "rel_change": [ + "-0.1", + "0.1" + ] + } + }, + "properties": { + "polled_attr": [ + "integration_time", + "0", + "pause_time", + "0", + "rcu_modes", + "0", + "state", + "0", + "status", + "0", + "subband", + "0", + "time_stamp", + "0", + "visibilities_imag", + "0", + "visibilities_real", + "0" + ] + } + } + } + } + } + } +} diff --git a/CDB/pypcc-sim-config.json b/CDB/pypcc-sim-config.json deleted file mode 100644 index c5288f56b6ee567093fedfde627aaece3e148e39..0000000000000000000000000000000000000000 --- a/CDB/pypcc-sim-config.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "servers": { - "PCC": { - "LTS": { - "PCC": { - "LTS/PCC/1": { - "properties": { - "OPC_Server_Name": [ - "pypcc-sim" - ], - "OPC_Server_Port": [ - "4842" - ], - "OPC_Time_Out": [ - "5.0" - ] - } - } - } - } - } - } -} diff --git a/CDB/sdp-sim-config.json b/CDB/sdp-sim-config.json deleted file mode 100644 index 64b841e1dacf36e1de9b3e20ea068d36f0011478..0000000000000000000000000000000000000000 --- a/CDB/sdp-sim-config.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "servers": { - "SDP": { - "LTS": { - "SDP": { - "LTS/SDP/1": { - "properties": { - "OPC_Server_Name": [ - "sdptr-sim" - ], - "OPC_Server_Port": [ - "4840" - ], - "OPC_Time_Out": [ - "5.0" - ] - } - } - } - } - }, - "SST": { - "LTS": { - "SST": { - "LTS/SST/1": { - "properties": { - "Statistics_Client_Port": [ - "5001" - ], - "OPC_Server_Name": [ - "sdptr-sim" - ], - "OPC_Server_Port": [ - "4840" - ], - "OPC_Time_Out": [ - "5.0" - ] - } - } - } - } - } - } -} diff --git a/CDB/stations/DTS_ConfigDb.json b/CDB/stations/DTS_ConfigDb.json new file mode 100644 index 0000000000000000000000000000000000000000..c5bbf009334e47e2dd0eed89dbddea6889f83933 --- /dev/null +++ b/CDB/stations/DTS_ConfigDb.json @@ -0,0 +1,227 @@ +{ + "servers": { + "APSCT": { + "STAT": { + "APSCT": { + "STAT/APSCT/1": { + "properties": { + "OPC_Server_Name": [ + "DESPi3.nfra.nl" + ], + "OPC_Server_Port": [ + "4843" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "APSPU": { + "STAT": { + "APSPU": { + "STAT/APSPU/1": { + "properties": { + "OPC_Server_Name": [ + "DESPi3.nfra.nl" + ], + "OPC_Server_Port": [ + "4842" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "RECV": { + "STAT": { + "RECV": { + "STAT/RECV/1": { + "properties": { + "OPC_Server_Name": [ + "DESPi3.nfra.nl" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "SDP": { + "STAT": { + "SDP": { + "STAT/SDP/1": { + "properties": { + "OPC_Server_Name": [ + "10.99.0.252" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_sdp_info_station_id_RW_default": [ + "902", + "902", + "902", + "902", + "902", + "902", + "902", + "902", + "902", + "902", + "902", + "902", + "902", + "902", + "902", + "902" + ] + } + } + } + } + }, + "SST": { + "STAT": { + "SST": { + "STAT/SST/1": { + "properties": { + "OPC_Server_Name": [ + "10.99.0.252" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [ + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1" + ], + "FPGA_sst_offload_hdr_ip_destination_address_RW_default": [ + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250" + ] + } + } + } + } + }, + "XST": { + "STAT": { + "XST": { + "STAT/XST/1": { + "properties": { + "OPC_Server_Name": [ + "10.99.0.252" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [ + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1", + "0c:c4:7a:c0:30:f1" + ], + "FPGA_xst_offload_hdr_ip_destination_address_RW_default": [ + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250" + ] + } + } + } + } + }, + "UNB2": { + "STAT": { + "UNB2": { + "STAT/UNB2/1": { + "properties": { + "OPC_Server_Name": [ + "DESPi3.nfra.nl" + ], + "OPC_Server_Port": [ + "4841" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + } + } +} diff --git a/CDB/stations/LTS_ConfigDb.json b/CDB/stations/LTS_ConfigDb.json new file mode 100644 index 0000000000000000000000000000000000000000..7c03ff1434f5e88860d6d174ad7ce952750606b6 --- /dev/null +++ b/CDB/stations/LTS_ConfigDb.json @@ -0,0 +1,220 @@ +{ + "servers": { + "RECV": { + "STAT": { + "RECV": { + "STAT/RECV/1": { + "properties": { + "OPC_Server_Name": [ + "ltspi.astron.nl" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Node_Path_prefix": [ + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "SDP": { + "STAT": { + "SDP": { + "STAT/SDP/1": { + "properties": { + "OPC_Server_Name": [ + "dop369.astron.nl" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_sdp_info_station_id_RW_default": [ + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901", + "901" + ] + } + } + } + } + }, + "SST": { + "STAT": { + "SST": { + "STAT/SST/1": { + "properties": { + "Statistics_Client_UDP_Port": [ + "5001" + ], + "Statistics_Client_TCP_Port": [ + "5101" + ], + "OPC_Server_Name": [ + "dop369.astron.nl" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [ + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd" + ], + "FPGA_sst_offload_hdr_ip_destination_address_RW_default": [ + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250" + ], + "FPGA_sst_offload_hdr_udp_destination_port_RW_default": [ + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001", + "5001" + ] + } + } + } + } + }, + "XST": { + "STAT": { + "XST": { + "STAT/XST/1": { + "properties": { + "Statistics_Client_UDP_Port": [ + "5002" + ], + "Statistics_Client_TCP_Port": [ + "5102" + ], + "OPC_Server_Name": [ + "dop369.astron.nl" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [ + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd", + "6c:2b:59:97:be:dd" + ], + "FPGA_xst_offload_hdr_ip_destination_address_RW_default": [ + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250", + "10.99.250.250" + ], + "FPGA_xst_offload_hdr_udp_destination_port_RW_default": [ + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002", + "5002" + ] + } + } + } + } + } + } +} diff --git a/CDB/stations/simulators_ConfigDb.json b/CDB/stations/simulators_ConfigDb.json new file mode 100644 index 0000000000000000000000000000000000000000..df2ffc1c1194282f7cc92cd0e7df1e5eb90b3a58 --- /dev/null +++ b/CDB/stations/simulators_ConfigDb.json @@ -0,0 +1,209 @@ +{ + "servers": { + "APSCT": { + "STAT": { + "APSCT": { + "STAT/APSCT/1": { + "properties": { + "OPC_Server_Name": [ + "apsct-sim" + ], + "OPC_Server_Port": [ + "4843" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "APSPU": { + "STAT": { + "APSPU": { + "STAT/APSPU/1": { + "properties": { + "OPC_Server_Name": [ + "apspu-sim" + ], + "OPC_Server_Port": [ + "4842" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "RECV": { + "STAT": { + "RECV": { + "STAT/RECV/1": { + "properties": { + "OPC_Server_Name": [ + "recv-sim" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "SDP": { + "STAT": { + "SDP": { + "STAT/SDP/1": { + "properties": { + "OPC_Server_Name": [ + "sdptr-sim" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "SST": { + "STAT": { + "SST": { + "STAT/SST/1": { + "properties": { + "OPC_Server_Name": [ + "sdptr-sim" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [ + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB" + ], + "FPGA_sst_offload_hdr_ip_destination_address_RW_default": [ + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1" + ] + } + } + } + } + }, + "XST": { + "STAT": { + "XST": { + "STAT/XST/1": { + "properties": { + "OPC_Server_Name": [ + "sdptr-sim" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [ + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB" + ], + "FPGA_xst_offload_hdr_ip_destination_address_RW_default": [ + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1" + ] + } + } + } + } + }, + "UNB2": { + "STAT": { + "UNB2": { + "STAT/UNB2/1": { + "properties": { + "OPC_Server_Name": [ + "unb2-sim" + ], + "OPC_Server_Port": [ + "4841" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + } + } +} diff --git a/CDB/test_ConfigDb.json b/CDB/test_ConfigDb.json index 879d73f275d0b7c275a01219cffcea92501be870..7f7e69512d0e9475e5d640ba6033ce7819106101 100644 --- a/CDB/test_ConfigDb.json +++ b/CDB/test_ConfigDb.json @@ -1,75 +1,75 @@ -{ - "servers": { - "PCC": { - "1": { - "PCC": { - "LTS/PCC/1": { - "properties": { - "OPC_Server_Name": [ - "ltspi.astron.nl" - ] - } - } - } - } - }, - "SDP": { - "1": { - "SDP": { - "LTS/SDP/1": { - "properties": { - "OPC_Server_Name": [ - "dop36.astron.nl" - ] - } - } - } - } - }, - "APSCTL": { - "1": { - "APSCTL": { - "LTS/APSCTL/1": { - "properties": { - "OPC_Server_Name": [ - "ltspi.astron.nl" - ], - "OPC_Server_Port": [ - "4844" - ], - "OPC_Time_Out": [ - "5.0" - ] - } - } - } - } - }, - "test_device": { - "1": { - "test_device": { - "LTS/test_device/1": { - "attribute_properties": { - "Ant_mask_RW": { - "archive_period": [ - "600000" - ] - } - }, - "properties": { - "OPC_Server_Name": [ - "host.docker.internal" - ], - "OPC_Server_Port": [ - "4842" - ], - "OPC_Time_Out": [ - "5.0" - ] - } - } - } - } - } - } -} +{ + "servers": { + "RECV": { + "1": { + "RECV": { + "STAT/RECV/1": { + "properties": { + "OPC_Server_Name": [ + "ltspi.astron.nl" + ] + } + } + } + } + }, + "SDP": { + "1": { + "SDP": { + "STAT/SDP/1": { + "properties": { + "OPC_Server_Name": [ + "dop36.astron.nl" + ] + } + } + } + } + }, + "APSCTL": { + "1": { + "APSCTL": { + "STAT/APSCTL/1": { + "properties": { + "OPC_Server_Name": [ + "ltspi.astron.nl" + ], + "OPC_Server_Port": [ + "4844" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "test_device": { + "1": { + "test_device": { + "STAT/test_device/1": { + "attribute_properties": { + "Ant_mask_RW": { + "archive_period": [ + "600000" + ] + } + }, + "properties": { + "OPC_Server_Name": [ + "host.docker.internal" + ], + "OPC_Server_Port": [ + "4842" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + } + } +} diff --git a/CDB/thijs_ConfigDb.json b/CDB/thijs_ConfigDb.json index 37ae6d7b66acb4bbb0be1fd36bfc78e2f93eba8e..6361533704fa01dc373b6c35f608772e89e9bdbc 100644 --- a/CDB/thijs_ConfigDb.json +++ b/CDB/thijs_ConfigDb.json @@ -1,134 +1,137 @@ -{ - "servers": { - "PCC": { - "1": { - "PCC": { - "LTS/PCC/1": { - "properties": { - "OPC_Server_Name": [ - "host.docker.internal" - ] - } - } - } - } - }, - "SDP": { - "1": { - "SDP": { - "LTS/SDP/1": { - "properties": { - "OPC_Server_Name": [ - "dop36.astron.nl" - ], - "OPC_Server_Port": [ - "4840" - ], - "OPC_Time_Out": [ - "5.0" - ] - } - } - } - } - }, - "example_device": { - "1": { - "example_device": { - "LTS/example_device/1": { - "properties": { - "OPC_Server_Name": [ - "host.docker.internal" - ], - "OPC_Server_Port": [ - "4842" - ], - "OPC_Time_Out": [ - "5.0" - ] - } - } - } - } - }, - "ini_device": { - "1": { - "ini_device": { - "LTS/ini_device/1": { - "properties": { - "OPC_Server_Name": [ - "host.docker.internal" - ], - "OPC_Server_Port": [ - "4844" - ], - "OPC_Time_Out": [ - "5.0" - ] - } - } - } - } - }, - "APSCTL": { - "1": { - "APSCTL": { - "LTS/APSCTL/1": { - "properties": { - "OPC_Server_Name": [ - "ltspi.astron.nl" - ], - "OPC_Server_Port": [ - "4844" - ], - "OPC_Time_Out": [ - "5.0" - ] - } - } - } - } - }, - "SST": { - "1": { - "SST": { - "LTS/SST/1": { - "properties": { - "Statistics_Client_Port": [ - "5001" - ], - "OPC_Server_Name": [ - "dop36.astron.nl" - ], - "OPC_Server_Port": [ - "4840" - ], - "OPC_Time_Out": [ - "5.0" - ] - } - } - } - } - }, - "SNMP": { - "1": { - "SNMP": { - "LTS/SNMP/1": { - "properties": { - "SNMP_community": [ - "public" - ], - "SNMP_host": [ - "192.168.178.17" - ], - "SNMP_timeout": [ - "5.0" - ] - } - } - } - } - } - } -} +{ + "servers": { + "RECV": { + "1": { + "RECV": { + "STAT/RECV/1": { + "properties": { + "OPC_Server_Name": [ + "host.docker.internal" + ] + } + } + } + } + }, + "SDP": { + "1": { + "SDP": { + "STAT/SDP/1": { + "properties": { + "OPC_Server_Name": [ + "dop36.astron.nl" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "example_device": { + "1": { + "example_device": { + "STAT/example_device/1": { + "properties": { + "OPC_Server_Name": [ + "host.docker.internal" + ], + "OPC_Server_Port": [ + "4842" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "ini_device": { + "1": { + "ini_device": { + "STAT/ini_device/1": { + "properties": { + "OPC_Server_Name": [ + "host.docker.internal" + ], + "OPC_Server_Port": [ + "4844" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "APSCTL": { + "1": { + "APSCTL": { + "STAT/APSCTL/1": { + "properties": { + "OPC_Server_Name": [ + "ltspi.astron.nl" + ], + "OPC_Server_Port": [ + "4844" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "SST": { + "1": { + "SST": { + "STAT/SST/1": { + "properties": { + "Statistics_Client_UDP_Port": [ + "5001" + ], + "Statistics_Client_TCP_Port": [ + "5101" + ], + "OPC_Server_Name": [ + "dop36.astron.nl" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "SNMP": { + "1": { + "SNMP": { + "STAT/SNMP/1": { + "properties": { + "SNMP_community": [ + "public" + ], + "SNMP_host": [ + "192.168.178.17" + ], + "SNMP_timeout": [ + "5.0" + ] + } + } + } + } + } + } +} diff --git a/CDB/thomas_ConfigDb.json b/CDB/thomas_ConfigDb.json deleted file mode 100644 index 33c19e162b8e15001759de58dfca22a82c2dd249..0000000000000000000000000000000000000000 --- a/CDB/thomas_ConfigDb.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "servers": { - "PCC": { - "LTS": { - "PCC": { - "LTS/PCC/1": { - "properties": { - "OPC_Server_Name": [ - "okeanos" - ] - } - } - } - } - }, - "SDP": { - "LTS": { - "SDP": { - "LTS/SDP/1": { - "properties": { - "OPC_Server_Name": [ - "okeanos" - ] - } - } - } - } - }, - "SST": { - "LTS": { - "SST": { - "LTS/SST/1": { - "properties": { - "OPC_Server_Name": [ - "okeanos" - ] - } - } - } - } - } - } -} diff --git a/CDB/thomas_arm64_ConfigDb.json b/CDB/thomas_arm64_ConfigDb.json deleted file mode 100644 index 4d010b690433d631ddadc7c14babbb31ec71c6ac..0000000000000000000000000000000000000000 --- a/CDB/thomas_arm64_ConfigDb.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "servers": { - "PCC": { - "LTS": { - "PCC": { - "LTS/PCC/1": { - "properties": { - "OPC_Server_Name": [ - "arm2" - ] - } - } - } - } - }, - "SDP": { - "LTS": { - "SDP": { - "LTS/SDP/1": { - "properties": { - "OPC_Server_Name": [ - "okeanos" - ] - } - } - } - } - }, - "SST": { - "LTS": { - "SST": { - "LTS/SST/1": { - "properties": { - "OPC_Server_Name": [ - "okeanos" - ] - } - } - } - } - } - } -} diff --git a/CDB/windows_ConfigDb.json b/CDB/windows_ConfigDb.json index c84fb3855372ba588de5bdef470d665b46ea6a99..ffc34ee01f505570da387bf6be81ec776f942f60 100644 --- a/CDB/windows_ConfigDb.json +++ b/CDB/windows_ConfigDb.json @@ -1,108 +1,108 @@ -{ - "servers": { - "PCC": { - "1": { - "PCC": { - "LTS/PCC/1": { - "properties": { - "OPC_Server_Name": [ - "host.docker.internal" - ] - } - } - } - } - }, - "SDP": { - "1": { - "SDP": { - "LTS/SDP/1": { - "properties": { - "OPC_Server_Name": [ - "host.docker.internal" - ] - } - } - } - } - }, - "ini_device": { - "1": { - "ini_device": { - "LTS/ini_device/1": { - "attribute_properties": { - "Ant_mask_RW": { - "archive_period": [ - "600000" - ] - } - }, - "properties": { - "SNMP_community": [ - "public" - ], - "SNMP_host": [ - "172.22.176.1" - ], - "SNMP_timeout": [ - "5.0" - ] - } - } - } - } - }, - "SNMP": { - "1": { - "SNMP": { - "LTS/SNMP/1": { - "attribute_properties": { - "Ant_mask_RW": { - "archive_period": [ - "600000" - ] - } - }, - "properties": { - "SNMP_community": [ - "public" - ], - "SNMP_host": [ - "172.22.176.1" - ], - "SNMP_timeout": [ - "5.0" - ] - } - } - } - } - }, - "test_device": { - "1": { - "test_device": { - "LTS/test_device/1": { - "attribute_properties": { - "Ant_mask_RW": { - "archive_period": [ - "600000" - ] - } - }, - "properties": { - "OPC_Server_Name": [ - "host.docker.internal" - ], - "OPC_Server_Port": [ - "4842" - ], - "OPC_Time_Out": [ - "5.0" - ] - } - } - } - } - } - } -} +{ + "servers": { + "RECV": { + "1": { + "RECV": { + "STAT/RECV/1": { + "properties": { + "OPC_Server_Name": [ + "host.docker.internal" + ] + } + } + } + } + }, + "SDP": { + "1": { + "SDP": { + "STAT/SDP/1": { + "properties": { + "OPC_Server_Name": [ + "host.docker.internal" + ] + } + } + } + } + }, + "ini_device": { + "1": { + "ini_device": { + "STAT/ini_device/1": { + "attribute_properties": { + "Ant_mask_RW": { + "archive_period": [ + "600000" + ] + } + }, + "properties": { + "SNMP_community": [ + "public" + ], + "SNMP_host": [ + "172.22.176.1" + ], + "SNMP_timeout": [ + "5.0" + ] + } + } + } + } + }, + "SNMP": { + "1": { + "SNMP": { + "STAT/SNMP/1": { + "attribute_properties": { + "Ant_mask_RW": { + "archive_period": [ + "600000" + ] + } + }, + "properties": { + "SNMP_community": [ + "public" + ], + "SNMP_host": [ + "172.22.176.1" + ], + "SNMP_timeout": [ + "5.0" + ] + } + } + } + } + }, + "test_device": { + "1": { + "test_device": { + "STAT/test_device/1": { + "attribute_properties": { + "Ant_mask_RW": { + "archive_period": [ + "600000" + ] + } + }, + "properties": { + "OPC_Server_Name": [ + "host.docker.internal" + ], + "OPC_Server_Port": [ + "4842" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + } + } +} diff --git a/README.md b/README.md index b7b4398a9581bf0771fa2e8a669f1e53c92b75d2..3f9bb0b2ba94b26e76a850d713a2dd048e218b9e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,49 @@ # Tango Station Control -Station Control software related to Tango devices. \ No newline at end of file +Station Control software related to Tango devices. + +# Installation + +## Prerequisites + +After checking out this repo, be sure to also check out the submodules: + +``` +git submodule init +git submodule update +``` + +You will also need: + +* docker +* docker-compose +* make +* bash + +## Bootstrap + +The bootstrap procedure is needed only once. First we build all docker containers, and load the initial configuration. This may take a while: + +``` +cd docker-compose +make bootstrap +``` + +If you lack access to LOFAR station hardware, configure the devices to use their simulators instead: + +``` +for sim in ../CDB/*-sim-config.json; do + ../sbin/update_ConfigDb.sh ../CDB${sim}-config.json +done +``` + +Now we can start all containers, and make sure everything is up: + +``` +make start +make status +``` + +If not, you can inspect why with `docker logs <container>`. The containers will automatically be restarted on reboot or failure. Stop them explicitly to bring them down (`make stop <container>`). + +Most notably, you will have web interfaces available at http://localhost:8888 (Jupyter Notebook), and http://localhost:3000 (Grafana). diff --git a/bin/dump_ConfigDb.sh b/bin/dump_ConfigDb.sh index bbd97a2208381c2fcf39710b9f908814313bdd7b..0dc634c458b76cd5d3c13e2d7dab6e905f66248a 100755 --- a/bin/dump_ConfigDb.sh +++ b/bin/dump_ConfigDb.sh @@ -1,6 +1,4 @@ -if [ ${#} -ne 1 ]; then - echo "You must provide a file name for the TANGO_HOST DB dump!" - exit -1 -fi +#!/bin/bash -docker exec -it dsconfig python -m dsconfig.dump > ${1} +# writes the JSON dump to stdout, Do not change -i into -it incompatible with gitlab ci! +docker exec -i "${CONTAINER_NAME_PREFIX}"dsconfig python -m dsconfig. diff --git a/bin/itango_console.sh b/bin/itango_console.sh index fb4c9b8a285adca3211633fb89e6e9e59bd01087..c2474781787257c6ab3ee0656659415f09380b29 100755 --- a/bin/itango_console.sh +++ b/bin/itango_console.sh @@ -1,2 +1,2 @@ #!/bin/bash -exec docker exec -it itango itango3 +exec docker exec -it "${CONTAINER_NAME_PREFIX}"itango itango3 diff --git a/bin/itango_shell.sh b/bin/itango_shell.sh index abab9ef8515fd5ce1a1bf2d6d452a538426a499a..334953de3cca29ff459f6c861637c0859b1da97b 100755 --- a/bin/itango_shell.sh +++ b/bin/itango_shell.sh @@ -1,2 +1,2 @@ #!/bin/bash -exec docker exec -it itango /bin/bash +exec docker exec -it "${CONTAINER_NAME_PREFIX}"itango /bin/bash diff --git a/bin/start-DS.sh b/bin/start-DS.sh deleted file mode 100755 index a9c9765d52db4fecd744117ef64938f20288511d..0000000000000000000000000000000000000000 --- a/bin/start-DS.sh +++ /dev/null @@ -1,42 +0,0 @@ -function help() -{ - why="${1}" - echo -e "*** Cannot start the Python device server.\n${why}\n\n* The Python file for the device server must be the 1st parameter that is provided.\n* The instance of this device server must be the 2nd parameter that is provided." - exit -1 -} - -# Check if the mandatory parameters are present: -# ${1}: device server's Python file -# ${2}: instance of the device server's executable in the configDB -case ${#} in - 0) - help "The device server's Python file and the instance are missing." - ;; - 1) - help "The device server's instance is missing." - ;; - *) - deviceServer="${1}" - shift - instance="${1}" - shift - ;; -esac - -# Find the path to the device server's Python file that is -# relative to the /hosthome directory (in Docker the user's -# mounted ${HOME}). -# ATTENTION -# This is assuming that the device server's Python file exists -# on the Docker's host in the user's ${HOME} directory. -runThis=$(basename ${deviceServer}) -runThis=${runThis//.sh/.py} -if [ -f ${runThis} ]; then - myDir=${PWD} -else - myDir=${PWD}/$(dirname ${deviceServer}) -fi -deviceServerPath=${myDir/${HOME}/\/hosthome} - -# Tango log lines start with a UNIX timestamp. Replace them with the UTC time. -docker exec -it itango python3 ${deviceServerPath}/${runThis} ${instance} ${@} | perl -ne 'use Time::Piece; s/^([0-9]+)/gmtime($1)->strftime("%F %T")/e; print;' diff --git a/bin/start-ds.sh b/bin/start-ds.sh new file mode 100755 index 0000000000000000000000000000000000000000..7b601c4c8f5e24ac56755ad08a1203a6cbba62d2 --- /dev/null +++ b/bin/start-ds.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# Serves as entrypoint script for docker containers + +# Check required support file exists +if [[ ! -f "/usr/local/bin/wait-for-it.sh" ]]; then + >&2 echo "/usr/local/bin/wait-for-it.sh file does not exist!" + exit 1 +fi + +# Check required environment variable is set +if [[ ! $TANGO_HOST ]]; then + >&2 echo "TANGO_HOST environment variable unset!" + exit 1 +fi + +# Check if configured for specific version +if [[ $TANGOSTATIONCONTROL ]]; then + # TODO (Corne): Download version from artifacts or pypi. + # Consider exit 2 an UnImplementedError + exit 2 +else + # Install the package, exit 1 if it fails + cd tangostationcontrol || exit 1 + mkdir -p /tmp/tangostationcontrol + python3 setup.py build --build-base /tmp/tangostationcontrol egg_info --egg-base /tmp/tangostationcontrol bdist_wheel --dist-dir /tmp/tangostationcontrol || exit 1 + # shellcheck disable=SC2012 + sudo pip install --force-reinstall "$(ls -Art /tmp/tangostationcontrol/*.whl | tail -n 1)" +fi + +/usr/local/bin/wait-for-it.sh "$TANGO_HOST" --timeout=30 --strict -- "$@" diff --git a/bin/start-jive.sh b/bin/start-jive.sh index fcbb9f8b5e95a4bbbfb6b2895c30d4d2a1914340..38e04ce1837f2351a46f0f5f3c55936825cf5d7b 100755 --- a/bin/start-jive.sh +++ b/bin/start-jive.sh @@ -1,9 +1,10 @@ +#!/bin/bash OS=$(uname) case ${OS} in Linux) display="" - XTRA_OPTIONS="-u $(id -u ${USER}):$(id -g ${USER}) -v /etc/passwd:/etc/passwd:ro -v /etc/groups:/etc/groups:ro" + XTRA_OPTIONS="-u $(id -u "${USER}"):$(id -g "${USER}") -v /etc/passwd:/etc/passwd:ro -v /etc/groups:/etc/groups:ro" ;; Darwin) @@ -28,10 +29,10 @@ else fi #docker run --rm -it --network host ${OPTIONS} nexus.engageska-portugal.pt/ska-docker/tango-java:latest ${command} ${@} -container_name=artefact.skatelescope.org/ska-tango-images/tango-java:9.3.3.2 -container=$(docker ps | egrep ${container_name} | cut -d' ' -f1) -if [ ! -z ${container} ]; then - docker exec -it ${container} ${command} ${@} +container_name=artefact.skao.int/ska-tango-images-tango-java:9.3.4 +container=$(docker ps | grep -E ${container_name} | cut -d' ' -f1) +if [ ! -z "${container}" ]; then + docker exec -it "${container}" ${command} "${@}" else echo "Container \"${container_name}\" is not running." fi diff --git a/bin/update_submodules.sh b/bin/update_submodules.sh new file mode 100755 index 0000000000000000000000000000000000000000..9dcb9745849c01bbf61b9ffae92c5c7cc21a5a8f --- /dev/null +++ b/bin/update_submodules.sh @@ -0,0 +1,2 @@ +#!/bin/bash +git submodule update --init diff --git a/bootstrap/etc/lofar20rc.sh b/bootstrap/etc/lofar20rc.sh index e9e8ac326b32aa0130c98005e2f472457bb65f42..6e4a5c9bc8d6a78c1b61cca02159ee01291d3805 100755 --- a/bootstrap/etc/lofar20rc.sh +++ b/bootstrap/etc/lofar20rc.sh @@ -12,13 +12,17 @@ ABSOLUTE_PATH=$(realpath $(dirname ${BASH_SOURCE})) export LOFAR20_DIR=${1:-$(realpath ${ABSOLUTE_PATH}/../..)} -# This needs to be modified for a development environment. -# Example: ~/lofar2.0/tango -# The current setting is for a production environment. -export TANGO_LOFAR_LOCAL_DIR=${LOFAR20_DIR}/ - -export TANGO_LOFAR_CONTAINER_DIR=/opt/lofar2.0/tango/ -export TANGO_LOFAR_CONTAINER_MOUNT=${TANGO_LOFAR_LOCAL_DIR}:${TANGO_LOFAR_CONTAINER_DIR}:rw +if [ ! -f "${LOFAR20_DIR}/.git/hooks/post-checkout" ]; then + alias git="cp ${LOFAR20_DIR}/bin/update_submodules.sh ${LOFAR20_DIR}/.git/hooks/post-checkout; cp ${LOFAR20_DIR}/bin/update_submodules.sh ${LOFAR20_DIR}/.git/hooks/post-merge; unalias git; git" +fi + +if [ ! -z ${CI_BUILD_ID+x} ]; then + export CONTAINER_NAME_PREFIX=${CI_BUILD_ID}- +elif [ ! -z ${CI_JOB_ID+x} ]; then + export CONTAINER_NAME_PREFIX=${CI_JOB_ID}- +else + unset CONTAINER_NAME_PREFIX +fi # This needs to be modified for a development environment. # In case you run multiple Docker networks on the same host in parallel, you need to specify a unique @@ -30,14 +34,10 @@ export NETWORK_MODE=tangonet # Example: export TANGO_HOST=station-xk25.astron.nl:10000 export TANGO_HOST=$(hostname):10000 - # # NO MODIFICATION BEYOND THIS POINT! # -# Provide the -v parameters for Docker and the -e ENV variables. -export TANGO_CONTAINER_ENV="-e TANGO_LOFAR_CONTAINER_DIR=${TANGO_LOFAR_CONTAINER_DIR}" - # Remove all LOFAR1 related environment modifications function remove_lofar() { diff --git a/bootstrap/etc/requirements.txt b/bootstrap/etc/requirements.txt index d7d6026bc20c52b6255dc0563d0780dc63c7f3aa..5502737a6308c9939be7a2fa4981707f965918ac 100644 --- a/bootstrap/etc/requirements.txt +++ b/bootstrap/etc/requirements.txt @@ -5,5 +5,5 @@ numpy opcua-client pyqtgraph PyQt5 -opcua >= 0.98.13 +asyncua dataclasses diff --git a/bootstrap/sbin/rebuild_system_from_scratch.sh b/bootstrap/sbin/rebuild_system_from_scratch.sh index 8335ba864b09c3008e1af310e1394d57dc6293fa..0af4d0b19d6fd85f48040265055235399c107a9e 100755 --- a/bootstrap/sbin/rebuild_system_from_scratch.sh +++ b/bootstrap/sbin/rebuild_system_from_scratch.sh @@ -112,7 +112,7 @@ function start_support_images() function start_lofar_images() { (cd ${HOME_DIR}/docker-compose - make start device-pcc + make start device-recv make start device-sdp) } diff --git a/devices/.stestr.conf b/devices/.stestr.conf deleted file mode 100644 index ddc59860d5117ed8bdc44faeea1d893760b5520e..0000000000000000000000000000000000000000 --- a/devices/.stestr.conf +++ /dev/null @@ -1,3 +0,0 @@ -[DEFAULT] -test_path=./test -top_dir=./ diff --git a/devices/LICENSE.txt b/devices/LICENSE.txt deleted file mode 100644 index 8a0eaeb196094a651006f51fd99c0c05cb16ccd6..0000000000000000000000000000000000000000 --- a/devices/LICENSE.txt +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright 2021 ASTRON Netherlands Institute for Radio Astronomy - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/devices/__init__.py b/devices/__init__.py deleted file mode 100644 index 82b2af0e96f75105253e501e47f8861218132f63..0000000000000000000000000000000000000000 --- a/devices/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from util.lofar_git import get_version - -__version__ = get_version() - diff --git a/devices/clients/comms_client.py b/devices/clients/comms_client.py deleted file mode 100644 index 011e1e62180e85f6bc17d72a6ee31eb5871ecb50..0000000000000000000000000000000000000000 --- a/devices/clients/comms_client.py +++ /dev/null @@ -1,131 +0,0 @@ -from threading import Thread -import time - -class CommClient(Thread): - """ - The ProtocolHandler class is the generic interface class between the tango attribute_wrapper and the outside world - """ - - def __init__(self, fault_func, streams, try_interval=2): - """ - - """ - self.fault_func = fault_func - self.try_interval = try_interval - self.streams = streams - self.stopping = False - self.connected = False - - super().__init__(daemon=True) - - def connect(self): - """ - Function used to connect to the client. - """ - self.connected = True - return True - - def disconnect(self): - """ - Function used to connect to the client. - """ - self.connected = False - - def run(self): - - # Explicitly connect - if not self.connect(): - # hardware or infra is down -- needs fixing first - self.fault_func() - return - - self.stopping = False - while not self.stopping: - # keep trying to connect - if not self.connected: - if self.connect(): - pass - else: - # we retry only once, to catch exotic network issues. if the infra or hardware is down, - # our device cannot help, and must be reinitialised after the infra or hardware is fixed. - self.fault_func() - return - - # keep checking if the connection is still alive - try: - while not self.stopping: - self.ping() - time.sleep(self.try_interval) - except Exception as e: - self.streams.error_stream("Fault condition in communication detected.", e) - - # technically, we may not have dropped the connection, but encounter a different error. so explicitly disconnect. - self.disconnect() - - # signal that we're disconnected - self.fault_func() - - # don't enter a spam-connect loop if faults immediately occur - time.sleep(self.try_interval) - - def ping(self): - return - - def stop(self): - """ - Stop connecting & disconnect. Can take a few seconds for the timeouts to hit. - """ - - if not self.ident: - # have not yet been started, so nothing to do - return - - self.stopping = True - self.join() - - self.disconnect() - - def setup_attribute(self, annotation, attribute): - """ - This function is responsible for providing the attribute_wrapper with a read/write function - How this is done is implementation specific. - The setup-attribute has access to the comms_annotation provided to the attribute wrapper to pass along to the comms client - as well as a reference to the attribute itself. - - It should do this by first calling: _setup_annotation and setup_value_conversion to get all data necceacry to configure the read/write functions. - It should then return the read and write functions to the attribute. - - MANDATORY: - annotation_outputs = _setup_annotation(annotation) - attribute_outputs = _setup_annotation(attribute) - (note: outputs are up to the user) - - REQUIRED: provide read and write functions to return, there are no restrictions on how these should be provided, - except that the read function takes a single input value and the write function returns a single value - - MANDATORY: - return read_function, write_function - - Examples: - - File system: get_mapping returns functions that read/write a fixed - number of bytes at a fixed location in a file. (SEEK) - - OPC-UA: traverse the OPC-UA tree until the node is found. - Then return the read/write functions for that node which automatically - convert values between Python and OPC-UA. - """ - raise NotImplementedError("the setup_attribute must be implemented and provide return a valid read/write function for the attribute") - - def _setup_annotation(self, annotation): - """ - This function is responsible for handling the annotation data provided by the attribute to configure the read/write function the client must provide. - This function should be called by setup_attribute - """ - raise NotImplementedError("the _setup_annotation must be implemented, content and outputs are up to the user") - - def setup_value_conversion(self, attribute): - """ - this function is responsible for setting up the value conversion between the client and the attribute. - This function should be called by setup_attribute - """ - raise NotImplementedError("the setup_value_conversion must be implemented, content and outputs are up to the user") - diff --git a/devices/clients/opcua_client.py b/devices/clients/opcua_client.py deleted file mode 100644 index dc85a6f4fe645fdb9b7c47e46e2e78a42bbcf41f..0000000000000000000000000000000000000000 --- a/devices/clients/opcua_client.py +++ /dev/null @@ -1,260 +0,0 @@ -from threading import Thread -import socket -import numpy -import opcua -from opcua import Client - -from clients.comms_client import CommClient - -__all__ = ["OPCUAConnection"] - -numpy_to_OPCua_dict = { - numpy.bool_: opcua.ua.VariantType.Boolean, - numpy.int8: opcua.ua.VariantType.SByte, - numpy.uint8: opcua.ua.VariantType.Byte, - numpy.int16: opcua.ua.VariantType.Int16, - numpy.uint16: opcua.ua.VariantType.UInt16, - numpy.int32: opcua.ua.VariantType.Int32, - numpy.uint32: opcua.ua.VariantType.UInt32, - numpy.int64: opcua.ua.VariantType.Int64, - numpy.uint64: opcua.ua.VariantType.UInt64, - numpy.datetime_data: opcua.ua.VariantType.DateTime, # is this the right type, does it even matter? - numpy.float32: opcua.ua.VariantType.Float, - numpy.double: opcua.ua.VariantType.Double, - numpy.float64: opcua.ua.VariantType.Double, - numpy.str_: opcua.ua.VariantType.String, - numpy.str: opcua.ua.VariantType.String, - str: opcua.ua.VariantType.String -} - -# <class 'numpy.bool_'> - -class OPCUAConnection(CommClient): - """ - Connects to OPC-UA in the foreground or background, and sends HELLO - messages to keep a check on the connection. On connection failure, reconnects once. - """ - - def start(self): - super().start() - - def __init__(self, address, namespace, timeout, fault_func, streams, try_interval=2): - """ - Create the OPC ua client and connect() to it and get the object node - """ - super().__init__(fault_func, streams, try_interval) - - self.client = Client(address, timeout) - - # Explicitly connect - if not self.connect(): - # hardware or infra is down -- needs fixing first - fault_func() - return - - - # determine namespace used - try: - if type(namespace) is str: - self.name_space_index = self.client.get_namespace_index(namespace) - elif type(namespace) is int: - self.name_space_index = namespace - - except Exception as e: - #TODO remove once SDP is fixed - self.streams.warn_stream("Cannot determine the OPC-UA name space index. Will try and use the default = 2.") - self.name_space_index = 2 - - self.obj = self.client.get_objects_node() - self.check_nodes() - - def _servername(self): - return self.client.server_url.geturl() - - def connect(self): - """ - Try to connect to the client - """ - - try: - self.streams.debug_stream("Connecting to server %s", self._servername()) - self.client.connect() - self.connected = True - self.streams.debug_stream("Connected to %s. Initialising.", self._servername()) - return True - except socket.error as e: - self.streams.error_stream("Could not connect to server %s: %s", self._servername(), e) - raise Exception("Could not connect to server %s", self._servername()) from e - - def check_nodes(self): - """ - function purely for debugging/development only. Simply lists all top level nodes and the nodes below that - """ - - for i in self.obj.get_children(): - print(i.get_browse_name()) - for j in i.get_children(): - try: - print(j.get_browse_name(), j.get_data_type_as_variant_type()) - except: - print(j.get_browse_name()) - finally: - pass - - - def disconnect(self): - """ - disconnect from the client - """ - self.connected = False # always force a reconnect, regardless of a successful disconnect - - try: - self.client.disconnect() - except Exception as e: - self.streams.error_stream("Disconnect from OPC-UA server %s failed: %s", self._servername(), e) - - def ping(self): - """ - ping the client to make sure the connection with the client is still functional. - """ - try: - self.client.send_hello() - except Exception as e: - raise Exception("Lost connection to server %s: %s", self._servername(), e) - - def _setup_annotation(self, annotation): - """ - This class's Implementation of the get_mapping function. returns the read and write functions - """ - - if isinstance(annotation, dict): - # check if required path inarg is present - if annotation.get('path') is None: - raise Exception("OPC-ua mapping requires a path argument in the annotation, was given: %s", annotation) - - path = annotation.get("path") # required - elif isinstance(annotation, list): - path = annotation - else: - raise Exception("OPC-ua mapping requires either a list of the path or dict with the path. Was given %s type containing: %s", type(annotation), annotation) - - try: - node = self.obj.get_child(path) - except Exception as e: - self.streams.error_stream("Could not get node: %s on server %s: %s", path, self._servername(), e) - raise Exception("Could not get node: %s on server %s", path, self._servername()) from e - - return node - - def setup_value_conversion(self, attribute): - """ - gives the client access to the attribute_wrapper object in order to access all data it could potentially need. - the OPC ua read/write functions require the dimensionality and the type to be known - """ - - dim_x = attribute.dim_x - dim_y = attribute.dim_y - ua_type = numpy_to_OPCua_dict[attribute.numpy_type] # convert the numpy type to a corresponding UA type - - return dim_x, dim_y, ua_type - - def setup_attribute(self, annotation, attribute): - """ - MANDATORY function: is used by the attribute wrapper to get read/write functions. must return the read and write functions - """ - - # process the annotation - node = self._setup_annotation(annotation) - - # get all the necessary data to set up the read/write functions from the attribute_wrapper - dim_x, dim_y, ua_type = self.setup_value_conversion(attribute) - - # configure and return the read/write functions - prot_attr = ProtocolAttribute(node, dim_x, dim_y, ua_type) - - try: - # NOTE: debug statement tries to get the qualified name, this may not always work. in that case forgo the name and just print the path - node_name = str(node.get_browse_name())[len("QualifiedName(2:"):] - self.streams.debug_stream("connected OPC ua node {} of type {} to attribute with dimensions: {} x {} ".format(str(node_name)[:len(node_name)-1], str(ua_type)[len("VariantType."):], dim_x, dim_y)) - except: - pass - # return the read/write functions - return prot_attr.read_function, prot_attr.write_function - - -class ProtocolAttribute: - """ - This class provides a small wrapper for the OPC ua read/write functions in order to better organise the code - """ - - def __init__(self, node, dim_x, dim_y, ua_type): - self.node = node - self.dim_y = dim_y - self.dim_x = dim_x - self.ua_type = ua_type - - def read_function(self): - """ - Read_R function - """ - value = numpy.array(self.node.get_value()) - - if self.dim_y + self.dim_x == 1: - # scalar - return value - elif self.dim_y != 0: - value = numpy.array(numpy.split(value, indices_or_sections=self.dim_y)) - elif self.dim_y + self.dim_x == 1: - value = [numpy.array(value)] - else: - value = numpy.array(value) - - return value - - - - def write_function(self, value): - """ - write_RW function - """ - - if self.dim_y != 0: - # flatten array, convert to python array - value = numpy.concatenate(value).tolist() - elif self.dim_x != 1: - # make sure it is a python array - value = value.tolist() if type(value) == numpy.ndarray else value - - try: - self.node.set_data_value(opcua.ua.uatypes.Variant(value=value, varianttype=self.ua_type)) - except (TypeError, opcua.ua.uaerrors.BadTypeMismatch) as e: - # A type conversion went wrong or there is a type mismatch. - # - # This is either the conversion us -> opcua in our client, or client -> server. - # Report all types involved to allow assessment of the location of the error. - if type(value) == list: - our_type = "list({dtype}) x ({dimensions})".format( - dtype=(type(value[0]).__name__ if value else ""), - dimensions=len(value)) - else: - our_type = "{dtype}".format( - dtype=type(value)) - - is_scalar = (self.dim_x + self.dim_y) == 1 - - if is_scalar: - expected_server_type = "{dtype} (scalar)".format( - dtype=self.ua_type) - else: - expected_server_type = "{dtype} x ({dim_x}, {dim_y})".format( - dtype=self.ua_type, - dim_x=self.dim_x, - dim_y=self.dim_y) - - actual_server_type = "{dtype} {dimensions}".format( - dtype=self.node.get_data_type_as_variant_type(), - dimensions=(self.node.get_array_dimensions() or "???")) - - attribute_name = self.node.get_display_name().to_string() - - raise TypeError(f"Cannot write value to OPC-UA attribute '{attribute_name}': tried to convert data type {our_type} to expected server type {expected_server_type}, server reports type {actual_server_type}") from e diff --git a/devices/clients/statistics_client.py b/devices/clients/statistics_client.py deleted file mode 100644 index 5d45ac472b52ac2f024dfd4a338cb3d03f4d3c77..0000000000000000000000000000000000000000 --- a/devices/clients/statistics_client.py +++ /dev/null @@ -1,126 +0,0 @@ -from queue import Queue -from threading import Thread -import logging -import numpy -import queue - -from .comms_client import CommClient -from .udp_receiver import UDPReceiver - -logger = logging.getLogger() - - -class StatisticsClient(CommClient): - """ - Collects statistics packets over UDP, forwards them to a StatisticsCollector, - and provides a CommClient interface to expose points to a Device Server. - """ - - def start(self): - super().start() - - def __init__(self, statistics_collector_class, host, port, fault_func, streams, try_interval=2, queuesize=1024): - """ - Create the statistics client and connect() to it and get the object node. - - statistics_collector_class: a subclass of StatisticsCollector that specialises in processing the received packets. - host: hostname to listen on - port: port number to listen on - """ - self.host = host - self.port = port - self.poll_timeout = 0.1 - self.queuesize = queuesize - self.statistics_collector_class = statistics_collector_class - - super().__init__(fault_func, streams, try_interval) - - # Explicitly connect - if not self.connect(): - # hardware or infra is down -- needs fixing first - fault_func() - return - - def queue_fill_percentage(self): - try: - return 100 * self.queue.qsize() / self.queue.maxsize if self.queue.maxsize else 0 - except NotImplementedError: - # some platforms don't have qsize(), nothing we can do here - return 0 - - def connect(self): - """ - Function used to connect to the client. - """ - if not self.connected: - self.queue = Queue(maxsize=self.queuesize) - self.udp = UDPReceiver(self.host, self.port, self.queue, self.poll_timeout) - self.statistics = self.statistics_collector_class(self.queue) - - return super().connect() - - def ping(self): - if not self.statistics.is_alive(): - raise Exception("Statistics processing thread died unexpectedly") - - if not self.udp.is_alive(): - raise Exception("UDP thread died unexpectedly") - - def disconnect(self): - # explicit disconnect, instead of waiting for the GC to kick in after "del" below - try: - self.statistics.disconnect() - except Exception: - # nothing we can do, but we should continue cleaning up - logger.log_exception("Could not disconnect statistics processing class") - - try: - self.udp.disconnect() - except Exception: - # nothing we can do, but we should continue cleaning up - logger.log_exception("Could not disconnect UDP receiver class") - - del self.udp - del self.statistics - del self.queue - - return super().disconnect() - - def setup_value_conversion(self, attribute): - """ - gives the client access to the attribute_wrapper object in order to access all data it could potentially need. - the OPC ua read/write functions require the dimensionality and the type to be known - """ - return - - def setup_attribute(self, annotation, attribute): - """ - MANDATORY function: is used by the attribute wrapper to get read/write functions. must return the read and write functions - """ - - parameter = annotation["parameter"] - - # get all the necessary data to set up the read/write functions from the attribute_wrapper - self.setup_value_conversion(attribute) - - # redirect to right object. this works as long as the parameter names are unique among them. - if annotation["type"] == "statistics": - def read_function(): - return self.statistics.parameters[parameter] - elif annotation["type"] == "udp": - def read_function(): - return self.udp.parameters[parameter] - elif annotation["type"] == "queue": - if parameter == "fill_percentage": - def read_function(): - return numpy.uint64(self.queue_fill_percentage()) - else: - raise ValueError("Unknown queue parameter requested: %s" % parameter) - - def write_function(value): - """ - Not used here - """ - pass - - return read_function, write_function diff --git a/devices/common/lofar_git.py b/devices/common/lofar_git.py deleted file mode 100644 index f4f6217280fe612fa2e9a7830c1f026c1e36a815..0000000000000000000000000000000000000000 --- a/devices/common/lofar_git.py +++ /dev/null @@ -1,84 +0,0 @@ -import git # pip3 install gitpython -import os -from functools import lru_cache - -def get_repo(starting_directory: str = os.path.dirname(os.path.abspath(__file__))) -> git.Repo: - """ Try finding the repository by traversing up the tree. - - By default, the repository containing this module is returned. - """ - - directory = starting_directory - - try: - return git.Repo(directory) - except git.InvalidGitRepositoryError: - pass - - # We now have to traverse up the tree - while directory != "/" and os.path.exists(directory): - # Go to parent - directory = os.path.abspath(directory + os.path.sep + "..") - - try: - return git.Repo(directory) - except git.InvalidGitRepositoryError: - pass - - raise git.InvalidGitRepositoryError("Could not find git repository root in {}".format(starting_directory)) - - -@lru_cache(maxsize=None) -def get_version(repo: git.Repo = None) -> str: - """ Return a version string for the current commit. - - There is a practical issue: the repository changes over time, f.e. switching branches with 'git checkout'. We want - to know the version that is running in memory, not the one that is on disk. - - As a work-around, we cache the version information, in that it is at least consistent. It is up to the caller - to request the version early enough. - - The version string is one of: - - <tag> - - <branch> [<commit>] - - In both cases, a "*" prefix indicates this code is not production ready. Code is considered production ready if - it is a tag and there are no local modifications. - - """ - - if repo is None: - repo = get_repo() - - commit = repo.commit() - tags = {tag.commit: tag for tag in repo.tags} - - if commit in tags: - # a tag = production ready - commit_str = "{}".format(tags[commit]) - production_ready = True - elif repo.head.is_detached: - # no active branch - commit_str = "<detached HEAD> [{}]".format(commit) - production_ready = False - else: - # HEAD of a branch - branch = repo.active_branch - commit_str = "{} [{}]".format(branch, commit) - production_ready = False - - if repo.is_dirty(): - production_ready = False - - return "{}{}".format("*" if not production_ready else "", commit_str) - - -# at least cache the current repo version immediately -try: - _ = get_version() -except: - pass - - -if __name__ == "__main__": - print(get_version()) diff --git a/devices/devices/apsctl.py b/devices/devices/apsctl.py deleted file mode 100644 index b555cb1b68ac5f76261c73d9723aa050316dc9d8..0000000000000000000000000000000000000000 --- a/devices/devices/apsctl.py +++ /dev/null @@ -1,205 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of the SDP project -# -# -# -# Distributed under the terms of the APACHE license. -# See LICENSE.txt for more info. - -""" SDP Device Server for LOFAR2.0 - -""" - -# TODO(Corne): Remove sys.path.append hack once packaging is in place! -import os, sys -currentdir = os.path.dirname(os.path.realpath(__file__)) -parentdir = os.path.dirname(currentdir) -sys.path.append(parentdir) - -# PyTango imports -from tango.server import run -from tango.server import device_property, attribute -from tango import AttrWriteType -# Additional import - -from clients.opcua_client import OPCUAConnection -from clients.attribute_wrapper import attribute_wrapper -from devices.hardware_device import hardware_device - -from common.lofar_logging import device_logging_to_python, log_exceptions -from common.lofar_git import get_version - -import numpy - -__all__ = ["APSCTL", "main"] - -@device_logging_to_python() -class APSCTL(hardware_device): - """ - - **Properties:** - - - Device Property - OPC_Server_Name - - Type:'DevString' - OPC_Server_Port - - Type:'DevULong' - OPC_Time_Out - - Type:'DevDouble' - """ - - # ----------------- - # Device Properties - # ----------------- - - OPC_Server_Name = device_property( - dtype='DevString', - mandatory=True - ) - - OPC_Server_Port = device_property( - dtype='DevULong', - mandatory=True - ) - - OPC_Time_Out = device_property( - dtype='DevDouble', - mandatory=True - ) - - # ---------- - # Attributes - # ---------- - - version_R = attribute(dtype=str, access=AttrWriteType.READ, fget=lambda self: get_version()) - - N_unb = 2 - N_fpga = 4 - N_ddr = 2 - N_qsfp = 6 - - # Central CP per Uniboard - UNB2_FPGA_DDR4_SLOT_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_DDR4_SLOT_TEMP_R"], datatype=numpy.double, dims=((N_unb * N_ddr), N_fpga)) - UNB2_I2C_bus_QSFP_STATUS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_I2C_bus_QSFP_STATUS_R"], datatype=numpy.int64, dims=((N_unb * N_fpga), N_qsfp)) - UNB2_I2C_bus_DDR4_STATUS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_I2C_bus_DDR4_STATUS_R"], datatype=numpy.int64, dims=(N_ddr, N_fpga)) - UNB2_I2C_bus_FPGA_PS_STATUS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_I2C_bus_FPGA_PS_STATUS_R"], datatype=numpy.int64, dims=(N_unb * N_fpga,)) - UNB2_translator_busy_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_translator_busy_R"], datatype=numpy.bool_) - - UNB2_Front_Panel_LED_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_Front_Panel_LED_RW"], datatype=numpy.uint8, dims=(N_unb,), access=AttrWriteType.READ_WRITE) - UNB2_Front_Panel_LED_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_Front_Panel_LED_R"], datatype=numpy.uint8, dims=(N_unb,)) - UNB2_EEPROM_Serial_Number_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_EEPROM_Serial_Number_R"], datatype=numpy.str, dims=(N_unb,)) - UNB2_EEPROM_Unique_ID_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_EEPROM_Unique_ID_R"], datatype=numpy.uint32, dims=(N_unb,)) - UNB2_FPGA_DDR4_SLOT_PART_NUMBER_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_DDR4_SLOT_PART_NUMBER_R"], datatype=numpy.str, dims=(N_unb * N_qsfp, N_fpga)) - UNB2_monitor_rate_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_monitor_rate_RW"], datatype=numpy.double, dims=(N_unb,), access=AttrWriteType.READ_WRITE) - UNB2_I2C_bus_STATUS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_I2C_bus_STATUS_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_I2C_bus_PS_STATUS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_I2C_bus_PS_STATUS_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_mask_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_mask_RW"], datatype=numpy.double, dims=(N_unb,), access=AttrWriteType.READ_WRITE) - UNB2_Power_ON_OFF_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_Power_ON_OFF_R"], datatype=numpy.double, dims=(N_unb,), access=AttrWriteType.READ_WRITE) - - UNB2_FPGA_QSFP_CAGE_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_QSFP_CAGE_TEMP_R"], datatype=numpy.double, dims=(N_unb * N_qsfp,N_fpga)) - UNB2_FPGA_QSFP_CAGE_LOS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_QSFP_CAGE_LOS_R"], datatype=numpy.uint8, dims=(N_unb * N_qsfp,N_fpga)) - UNB2_FPGA_POL_HGXB_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_HGXB_VOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_FPGA_POL_HGXB_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_HGXB_IOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_FPGA_POL_HGXB_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_HGXB_TEMP_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_FPGA_POL_PGM_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_PGM_VOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_FPGA_POL_PGM_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_PGM_IOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_FPGA_POL_PGM_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_PGM_TEMP_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_FPGA_POL_RXGXB_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_RXGXB_VOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_FPGA_POL_RXGXB_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_RXGXB_IOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_FPGA_POL_RXGXB_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_RXGXB_TEMP_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_FPGA_POL_TXGXB_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_TXGXB_VOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_FPGA_POL_TXGXB_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_TXGXB_IOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_POL_FPGA_TXGXB_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_FPGA_TXGXB_TEMP_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_POL_FPGA_CORE_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_FPGA_CORE_VOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_FPGA_POL_CORE_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_CORE_IOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_FPGA_POL_CORE_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_CORE_TEMP_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_FPGA_POL_ERAM_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_ERAM_VOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_FPGA_POL_ERAM_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_ERAM_IOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_FPGA_POL_ERAM_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_ERAM_TEMP_R"], datatype=numpy.double, dims=(N_unb,N_fpga)) - UNB2_POL_CLOCK_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_CLOCK_VOUT_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_POL_CLOCK_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_CLOCK_IOUT_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_POL_CLOCK_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_CLOCK_TEMP_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_POL_SWITCH_1V2_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_1V2_VOUT_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_POL_SWITCH_1V2_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_1V2_IOUT_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_POL_SWITCH_1V2_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_1V2_TEMP_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_POL_SWITCH_PHY_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_PHY_VOUT_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_POL_SWITCH_PHY_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_PHY_IOUT_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_POL_SWITCH_PHY_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_PHY_TEMP_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_POL_QSFP_N01_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_QSFP_N01_VOUT_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_POL_QSFP_N01_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_QSFP_N01_IOUT_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_POL_QSFP_N01_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_QSFP_N01_TEMP_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_POL_QSFP_N23_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_QSFP_N23_VOUT_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_POL_QSFP_N23_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_QSFP_N23_IOUT_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_POL_QSFP_N23_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_QSFP_N23_TEMP_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_DC_DC_48V_12V_VIN_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_DC_DC_48V_12V_VIN_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_DC_DC_48V_12V_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_DC_DC_48V_12V_VOUT_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_DC_DC_48V_12V_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_DC_DC_48V_12V_IOUT_R"], datatype=numpy.double, dims=(N_unb,)) - UNB2_DC_DC_48V_12V_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_DC_DC_48V_12V_TEMP_R"], datatype=numpy.double, dims=(N_unb,)) - - - # QualifiedName(2: UNB2_on) - # QualifiedName(2: UNB2_off) - @log_exceptions() - def delete_device(self): - """Hook to delete resources allocated in init_device. - - This method allows for any memory or other resources allocated in the - init_device method to be released. This method is called by the device - destructor and by the device Init command (a Tango built-in). - """ - self.debug_stream("Shutting down...") - - self.Off() - self.debug_stream("Shut down. Good bye.") - - # -------- - # overloaded functions - # -------- - @log_exceptions() - def configure_for_off(self): - """ user code here. is called when the state is set to OFF """ - # Stop keep-alive - try: - self.opcua_connection.stop() - except Exception as e: - self.warn_stream("Exception while stopping OPC ua connection in configure_for_off function: {}. Exception ignored".format(e)) - - @log_exceptions() - def configure_for_initialise(self): - """ user code here. is called when the sate is set to INIT """ - """Initialises the attributes and properties of the PCC.""" - - # set up the OPC ua client - self.OPCua_client = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), "http://lofar.eu", self.OPC_Time_Out, self.Fault, self) - - # map an access helper class - for i in self.attr_list(): - try: - i.set_comm_client(self.OPCua_client) - except Exception as e: - # use the pass function instead of setting read/write fails - i.set_pass_func() - self.warn_stream("error while setting the APSCTL attribute {} read/write function. {}".format(i, e)) - - self.OPCua_client.start() - - # -------- - # Commands - # -------- - -# ---------- -# Run server -# ---------- -def main(args=None, **kwargs): - """Main function of the SDP module.""" - - from devices.common.lofar_logging import configure_logger - configure_logger() - - return run((APSCTL,), args=args, **kwargs) - - -if __name__ == '__main__': - main() - diff --git a/devices/devices/hardware_device.py b/devices/devices/hardware_device.py deleted file mode 100644 index a4da09297a6696c4fb5a31e2359b63958cb4eb4d..0000000000000000000000000000000000000000 --- a/devices/devices/hardware_device.py +++ /dev/null @@ -1,193 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of the PCC project -# -# -# -# Distributed under the terms of the APACHE license. -# See LICENSE.txt for more info. - -""" PCC Device Server for LOFAR2.0 - -""" - -from abc import ABCMeta, abstractmethod - -# PyTango imports -from tango.server import Device, command, DeviceMeta -from tango import DevState, DebugIt -# Additional import - -from clients.attribute_wrapper import attribute_wrapper -from common.lofar_logging import log_exceptions - -__all__ = ["hardware_device"] - -from devices.device_decorators import only_in_states, fault_on_error - -import logging -logger = logging.getLogger() - -class AbstractDeviceMetas(DeviceMeta, ABCMeta): - ''' Collects meta classes to allow hardware_device to be both a Device and an ABC. ''' - pass - -#@log_exceptions() -class hardware_device(Device, metaclass=AbstractDeviceMetas): - """ - - **Properties:** - - States are as follows: - INIT = Device is initialising. - STANDBY = Device is initialised, but pends external configuration and an explicit turning on, - ON = Device is fully configured, functional, controls the hardware, and is possibly actively running, - FAULT = Device detected an unrecoverable error, and is thus malfunctional, - OFF = Device is turned off, drops connection to the hardware, - - The following state transitions are implemented: - boot -> OFF: Triggered by tango. Device will be instantiated, - OFF -> INIT: Triggered by device. Device will initialise (connect to hardware, other devices), - INIT -> STANDBY: Triggered by device. Device is initialised, and is ready for additional configuration by the user, - STANDBY -> ON: Triggered by user. Device reports to be functional, - * -> FAULT: Triggered by device. Device has degraded to malfunctional, for example because the connection to the hardware is lost, - * -> FAULT: Triggered by user. Emulate a forced malfunction for integration testing purposes, - * -> OFF: Triggered by user. Device is turned off. Triggered by the Off() command, - FAULT -> INIT: Triggered by user. Device is reinitialised to recover from an error, - - The user triggers their transitions by the commands reflecting the target state (Initialise(), On(), Fault()). - """ - - @classmethod - def attr_list(cls): - """ Return a list of all the attribute_wrapper members of this class. """ - return [v for k, v in cls.__dict__.items() if type(v) == attribute_wrapper] - - def setup_value_dict(self): - """ set the initial value for all the attribute wrapper objects""" - - self.value_dict = {i: i.initial_value() for i in self.attr_list()} - - def init_device(self): - """ Instantiates the device in the OFF state. """ - - # NOTE: Will delete_device first, if necessary - Device.init_device(self) - - self.set_state(DevState.OFF) - - # -------- - # Commands - # -------- - - @command() - @only_in_states([DevState.FAULT, DevState.OFF]) - @DebugIt() - @fault_on_error() - @log_exceptions() - def Initialise(self): - """ - Command to ask for initialisation of this device. Can only be called in FAULT or OFF state. - - :return:None - """ - self.set_state(DevState.INIT) - self.setup_value_dict() - - self.configure_for_initialise() - - self.set_state(DevState.STANDBY) - - @command() - @only_in_states([DevState.STANDBY, DevState.ON]) - @DebugIt() - @fault_on_error() - @log_exceptions() - def On(self): - """ - Command to ask for initialisation of this device. Can only be called in FAULT or OFF state. - - :return:None - """ - if self.get_state() == DevState.ON: - # Already on. Don't complain. - logger.warning("Requested to go to ON state, but am already in ON state.") - return - - self.configure_for_on() - self.set_state(DevState.ON) - - @command() - @DebugIt() - @log_exceptions() - def Off(self): - """ - Command to ask for shutdown of this device. - - :return:None - """ - if self.get_state() == DevState.OFF: - # Already off. Don't complain. - return - - # Turn off - self.set_state(DevState.OFF) - - self.configure_for_off() - - # Turn off again, in case of race conditions through reconnecting - self.set_state(DevState.OFF) - - @command() - @only_in_states([DevState.ON, DevState.INIT, DevState.STANDBY, DevState.FAULT]) - @DebugIt() - @log_exceptions() - def Fault(self): - """ - FAULT state is used to indicate our connection with the OPC-UA server is down. - - This device will try to reconnect once, and transition to the ON state on success. - - If reconnecting fails, the user needs to call Initialise() to retry to restart this device. - - :return:None - """ - if self.get_state() == DevState.FAULT: - # Already faulting. Don't complain. - logger.warning("Requested to go to FAULT state, but am already in FAULT state.") - return - - self.configure_for_fault() - self.set_state(DevState.FAULT) - - - # functions that can or must be overloaded - def configure_for_fault(self): - pass - - @abstractmethod - def configure_for_off(self): - pass - - def configure_for_on(self): - pass - - @abstractmethod - def configure_for_initialise(self): - pass - - def always_executed_hook(self): - """Method always executed before any TANGO command is executed.""" - pass - - def delete_device(self): - """Hook to delete resources allocated in init_device. - - This method allows for any memory or other resources allocated in the - init_device method to be released. This method is called by the device - destructor and by the device Init command (a Tango built-in). - """ - self.debug_stream("Shutting down...") - - self.Off() - self.debug_stream("Shut down. Good bye.") diff --git a/devices/devices/pcc.py b/devices/devices/pcc.py deleted file mode 100644 index 0db21b41e7c609c934345e0b0dafdea9e9e08efb..0000000000000000000000000000000000000000 --- a/devices/devices/pcc.py +++ /dev/null @@ -1,260 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of the PCC project -# -# -# -# Distributed under the terms of the APACHE license. -# See LICENSE.txt for more info. - -""" PCC Device Server for LOFAR2.0 - -""" - -# TODO(Corne): Remove sys.path.append hack once packaging is in place! -import os, sys -currentdir = os.path.dirname(os.path.realpath(__file__)) -parentdir = os.path.dirname(currentdir) -sys.path.append(parentdir) - -# PyTango imports -from tango import DebugIt -from tango.server import run, command -from tango.server import device_property, attribute -from tango import AttrWriteType -import numpy -# Additional import - -from device_decorators import * - -from clients.opcua_client import OPCUAConnection -from clients.attribute_wrapper import attribute_wrapper -from devices.hardware_device import hardware_device -from common.lofar_logging import device_logging_to_python, log_exceptions -from common.lofar_git import get_version - -__all__ = ["PCC", "main"] - -@device_logging_to_python() -class PCC(hardware_device): - """ - - **Properties:** - - - Device Property - OPC_Server_Name - - Type:'DevString' - OPC_Server_Port - - Type:'DevULong' - OPC_Time_Out - - Type:'DevDouble' - """ - - # ----------------- - # Device Properties - # ----------------- - - OPC_Server_Name = device_property( - dtype='DevString', - mandatory=True - ) - - OPC_Server_Port = device_property( - dtype='DevULong', - mandatory=True - ) - - OPC_Time_Out = device_property( - dtype='DevDouble', - mandatory=True - ) - OPC_namespace = device_property( - dtype='DevString', - mandatory=False - ) - - # ---------- - # Attributes - # ---------- - version_R = attribute(dtype=str, access=AttrWriteType.READ, fget=lambda self: get_version()) - Ant_mask_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:Ant_mask_RW"], datatype=numpy.bool_, dims=(3, 32), access=AttrWriteType.READ_WRITE) - CLK_Enable_PWR_R = attribute_wrapper(comms_annotation=["2:PCC", "2:CLK_Enable_PWR_R"], datatype=numpy.bool_) - CLK_I2C_STATUS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:CLK_I2C_STATUS_R"], datatype=numpy.int64) - CLK_PLL_error_R = attribute_wrapper(comms_annotation=["2:PCC", "2:CLK_PLL_error_R"], datatype=numpy.bool_) - CLK_PLL_locked_R = attribute_wrapper(comms_annotation=["2:PCC", "2:CLK_PLL_locked_R"], datatype=numpy.bool_) - CLK_monitor_rate_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:CLK_monitor_rate_RW"], datatype=numpy.int64, access=AttrWriteType.READ_WRITE) - CLK_translator_busy_R = attribute_wrapper(comms_annotation=["2:PCC", "2:CLK_translator_busy_R"], datatype=numpy.bool_) - HBA_element_beamformer_delays_R = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_beamformer_delays_R"], datatype=numpy.int64, dims=(32, 96)) - HBA_element_beamformer_delays_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_beamformer_delays_RW"], datatype=numpy.int64, dims=(32, 96), access=AttrWriteType.READ_WRITE) - HBA_element_led_R = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_led_R"], datatype=numpy.int64, dims=(32, 96)) - HBA_element_led_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_led_RW"], datatype=numpy.int64, dims=(32, 96), access=AttrWriteType.READ_WRITE) - HBA_element_LNA_pwr_R = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_LNA_pwr_R"], datatype=numpy.int64, dims=(32, 96)) - HBA_element_LNA_pwr_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_LNA_pwr_RW"], datatype=numpy.int64, dims=(32, 96), access=AttrWriteType.READ_WRITE) - HBA_element_pwr_R = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_pwr_R"], datatype=numpy.int64, dims=(32, 96)) - HBA_element_pwr_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_pwr_RW"], datatype=numpy.int64, dims=(32, 96), access=AttrWriteType.READ_WRITE) - RCU_ADC_lock_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_ADC_lock_R"], datatype=numpy.int64, dims=(3, 32)) - RCU_attenuator_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_attenuator_R"], datatype=numpy.int64, dims=(3, 32)) - RCU_attenuator_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_attenuator_RW"], datatype=numpy.int64, dims=(3, 32), access=AttrWriteType.READ_WRITE) - RCU_band_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_band_R"], datatype=numpy.int64, dims=(3, 32)) - RCU_band_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_band_RW"], datatype=numpy.int64, dims=(3, 32), access=AttrWriteType.READ_WRITE) - RCU_I2C_STATUS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_I2C_STATUS_R"], datatype=numpy.int64, dims=(32,)) - RCU_ID_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_ID_R"], datatype=numpy.int64, dims=(32,)) - RCU_LED0_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_LED0_R"], datatype=numpy.bool_, dims=(32,)) - RCU_LED0_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_LED0_RW"], datatype=numpy.bool_, dims=(32,), access=AttrWriteType.READ_WRITE) - RCU_LED1_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_LED1_R"], datatype=numpy.bool_, dims=(32,)) - RCU_LED1_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_LED1_RW"], datatype=numpy.bool_, dims=(32,), access=AttrWriteType.READ_WRITE) - RCU_mask_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_mask_RW"], datatype=numpy.bool_, dims=(32,), access=AttrWriteType.READ_WRITE) - RCU_monitor_rate_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_monitor_rate_RW"], datatype=numpy.int64, access=AttrWriteType.READ_WRITE) - RCU_Pwr_dig_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_Pwr_dig_R"], datatype=numpy.bool_, dims=(32,)) - RCU_temperature_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_temperature_R"], datatype=numpy.float64, dims=(32,)) - RCU_translator_busy_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_translator_busy_R"], datatype=numpy.bool_) - RCU_version_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_version_R"], datatype=numpy.str_, dims=(32,)) - - @log_exceptions() - def delete_device(self): - """Hook to delete resources allocated in init_device. - - This method allows for any memory or other resources allocated in the - init_device method to be released. This method is called by the device - destructor and by the device Init command (a Tango built-in). - """ - self.debug_stream("Shutting down...") - - self.Off() - self.debug_stream("Shut down. Good bye.") - - # -------- - # overloaded functions - # -------- - @log_exceptions() - def configure_for_off(self): - """ user code here. is called when the state is set to OFF """ - # Stop keep-alive - try: - self.opcua_connection.stop() - except Exception as e: - self.warn_stream("Exception while stopping OPC ua connection in configure_for_off function: {}. Exception ignored".format(e)) - - @log_exceptions() - def configure_for_initialise(self): - """ user code here. is called when the state is set to INIT """ - - # Init the dict that contains function to OPC-UA function mappings. - self.function_mapping = {} - self.function_mapping["RCU_on"] = {} - self.function_mapping["RCU_off"] = {} - self.function_mapping["CLK_on"] = {} - self.function_mapping["CLK_off"] = {} - - # set up the OPC ua client - self.OPCua_client = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), "http://lofar.eu", - self.OPC_Time_Out, self.Fault, self) - - # map an access helper class - for i in self.attr_list(): - try: - i.set_comm_client(self.OPCua_client) - except Exception as e: - # use the pass function instead of setting read/write fails - i.set_pass_func() - self.warn_stream("error while setting the PCC attribute {} read/write function. {}".format(i, e)) - - self.OPCua_client.start() - - - - # -------- - # Commands - # -------- - @command() - @DebugIt() - @only_when_on() - @fault_on_error() - def RCU_off(self): - """ - - :return:None - """ - self.function_mapping["RCU_off"]() - - @command() - @DebugIt() - @only_when_on() - @fault_on_error() - def RCU_on(self): - """ - - :return:None - """ - self.function_mapping["RCU_on"]() - - @command() - @DebugIt() - @only_when_on() - @fault_on_error() - def ADC_on(self): - """ - - :return:None - """ - self.function_mapping["ADC_on"]() - - @command() - @DebugIt() - @only_when_on() - @fault_on_error() - def RCU_update(self): - """ - - :return:None - """ - self.function_mapping["RCU_update"]() - - @command() - @DebugIt() - @only_when_on() - @fault_on_error() - def CLK_off(self): - """ - - :return:None - """ - self.function_mapping["CLK_off"]() - - @command() - @DebugIt() - @only_when_on() - @fault_on_error() - def CLK_on(self): - """ - - :return:None - """ - self.function_mapping["CLK_on"]() - - @command() - @DebugIt() - @only_when_on() - @fault_on_error() - def CLK_PLL_setup(self): - """ - - :return:None - """ - self.function_mapping["CLK_PLL_setup"]() - - -# ---------- -# Run server -# ---------- -def main(args=None, **kwargs): - """Main function of the PCC module.""" - - from common.lofar_logging import configure_logger - configure_logger() - - return run((PCC,), args=args, **kwargs) - - -if __name__ == '__main__': - main() diff --git a/devices/devices/sdp/sdp.py b/devices/devices/sdp/sdp.py deleted file mode 100644 index c78c1d042fae448adac4b9e24901a053f1683fe1..0000000000000000000000000000000000000000 --- a/devices/devices/sdp/sdp.py +++ /dev/null @@ -1,183 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of the SDP project -# -# -# -# Distributed under the terms of the APACHE license. -# See LICENSE.txt for more info. - -""" SDP Device Server for LOFAR2.0 - -""" - -# TODO(Corne): Remove sys.path.append hack once packaging is in place! -import os, sys -currentdir = os.path.dirname(os.path.realpath(__file__)) -parentdir = os.path.dirname(currentdir) -parentdir = os.path.dirname(parentdir) -sys.path.append(parentdir) - -# PyTango imports -from tango.server import run -from tango.server import device_property, attribute -from tango import AttrWriteType -# Additional import - -from clients.opcua_client import OPCUAConnection -from clients.attribute_wrapper import attribute_wrapper -from devices.hardware_device import hardware_device - -from common.lofar_logging import device_logging_to_python, log_exceptions -from common.lofar_git import get_version - -import numpy - -__all__ = ["SDP", "main"] - -@device_logging_to_python() -class SDP(hardware_device): - """ - - **Properties:** - - - Device Property - OPC_Server_Name - - Type:'DevString' - OPC_Server_Port - - Type:'DevULong' - OPC_Time_Out - - Type:'DevDouble' - """ - - # ----------------- - # Device Properties - # ----------------- - - OPC_Server_Name = device_property( - dtype='DevString', - mandatory=True - ) - - OPC_Server_Port = device_property( - dtype='DevULong', - mandatory=True - ) - - OPC_Time_Out = device_property( - dtype='DevDouble', - mandatory=True - ) - - # ---------- - # Attributes - # ---------- - - version_R = attribute(dtype=str, access=AttrWriteType.READ, fget=lambda self: get_version()) - - # SDP will switch from FPGA_mask_RW to TR_FPGA_mask_RW, offer both for now as its a critical flag - FPGA_firmware_version_R = attribute_wrapper(comms_annotation=["2:FPGA_firmware_version_R"], datatype=numpy.str_, dims=(16,)) - FPGA_hardware_version_R = attribute_wrapper(comms_annotation=["2:FPGA_hardware_version_R"], datatype=numpy.str_, dims=(16,)) - FPGA_mask_R = attribute_wrapper(comms_annotation=["2:FPGA_mask_R"], datatype=numpy.bool_, dims=(16,)) - FPGA_mask_RW = attribute_wrapper(comms_annotation=["2:FPGA_mask_RW"], datatype=numpy.bool_, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_processing_enable_R = attribute_wrapper(comms_annotation=["2:FPGA_processing_enable_R"], datatype=numpy.bool_, dims=(16,)) - FPGA_processing_enable_RW = attribute_wrapper(comms_annotation=["2:FPGA_processing_enable_RW"], datatype=numpy.bool_, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_scrap_R = attribute_wrapper(comms_annotation=["2:FPGA_scrap_R"], datatype=numpy.int32, dims=(8192,)) - FPGA_scrap_RW = attribute_wrapper(comms_annotation=["2:FPGA_scrap_RW"], datatype=numpy.int32, dims=(8192,), access=AttrWriteType.READ_WRITE) - FPGA_sdp_info_antenna_band_index_R = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_antenna_band_index_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_sdp_info_block_period_R = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_block_period_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_sdp_info_f_adc_R = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_f_adc_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_sdp_info_f_sub_type_R = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_f_sub_type_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_sdp_info_nyquist_sampling_zone_index_R = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_nyquist_sampling_zone_index_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_sdp_info_nyquist_sampling_zone_index_RW = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_nyquist_sampling_zone_index_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_sdp_info_observation_id_R = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_observation_id_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_sdp_info_observation_id_RW = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_observation_id_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_sdp_info_station_id_R = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_station_id_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_sdp_info_station_id_RW = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_station_id_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_status_R = attribute_wrapper(comms_annotation=["2:FPGA_status_R"], datatype=numpy.bool_, dims=(16,)) - FPGA_temp_R = attribute_wrapper(comms_annotation=["2:FPGA_temp_R"], datatype=numpy.float_, dims=(16,)) - FPGA_version_R = attribute_wrapper(comms_annotation=["2:FPGA_version_R"], datatype=numpy.str_, dims=(16,)) - FPGA_weights_R = attribute_wrapper(comms_annotation=["2:FPGA_weights_R"], datatype=numpy.int16, dims=(16, 12 * 488 * 2)) - FPGA_weights_RW = attribute_wrapper(comms_annotation=["2:FPGA_weights_RW"], datatype=numpy.int16, dims=(16, 12 * 488 * 2), access=AttrWriteType.READ_WRITE) - FPGA_wg_amplitude_R = attribute_wrapper(comms_annotation=["2:FPGA_wg_amplitude_R"], datatype=numpy.float_, dims=(16, 12)) - FPGA_wg_amplitude_RW = attribute_wrapper(comms_annotation=["2:FPGA_wg_amplitude_RW"], datatype=numpy.float_, dims=(16, 12), access=AttrWriteType.READ_WRITE) - FPGA_wg_enable_R = attribute_wrapper(comms_annotation=["2:FPGA_wg_enable_R"], datatype=numpy.bool_, dims=(16, 12)) - FPGA_wg_enable_RW = attribute_wrapper(comms_annotation=["2:FPGA_wg_enable_RW"], datatype=numpy.bool_, dims=(16, 12), access=AttrWriteType.READ_WRITE) - FPGA_wg_frequency_R = attribute_wrapper(comms_annotation=["2:FPGA_wg_frequency_R"], datatype=numpy.float_, dims=(16, 12)) - FPGA_wg_frequency_RW = attribute_wrapper(comms_annotation=["2:FPGA_wg_frequency_RW"], datatype=numpy.float_, dims=(16, 12), access=AttrWriteType.READ_WRITE) - FPGA_wg_phase_R = attribute_wrapper(comms_annotation=["2:FPGA_wg_phase_R"], datatype=numpy.float_, dims=(16, 12)) - FPGA_wg_phase_RW = attribute_wrapper(comms_annotation=["2:FPGA_wg_phase_R"], datatype=numpy.float_, dims=(16, 12), access=AttrWriteType.READ_WRITE) - TR_busy_R = attribute_wrapper(comms_annotation=["2:TR_busy_R"], datatype=numpy.bool_) - TR_reload_RW = attribute_wrapper(comms_annotation=["2:TR_reload_RW"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) - TR_software_version_R = attribute_wrapper(comms_annotation=["2:TR_software_version_R"], datatype=numpy.str_) - TR_tod_R = attribute_wrapper(comms_annotation=["2:TR_tod_R"], datatype=numpy.uint64) - TR_uptime_R = attribute_wrapper(comms_annotation=["2:TR_uptime_R"], datatype=numpy.uint64) - - def always_executed_hook(self): - """Method always executed before any TANGO command is executed.""" - pass - - @log_exceptions() - def delete_device(self): - """Hook to delete resources allocated in init_device. - - This method allows for any memory or other resources allocated in the - init_device method to be released. This method is called by the device - destructor and by the device Init command (a Tango built-in). - """ - self.debug_stream("Shutting down...") - - self.Off() - self.debug_stream("Shut down. Good bye.") - - # -------- - # overloaded functions - # -------- - @log_exceptions() - def configure_for_off(self): - """ user code here. is called when the state is set to OFF """ - - # Stop keep-alive - try: - self.opcua_connection.stop() - except Exception as e: - self.warn_stream("Exception while stopping OPC ua connection in configure_for_off function: {}. Exception ignored".format(e)) - - @log_exceptions() - def configure_for_initialise(self): - """ user code here. is called when the sate is set to INIT """ - """Initialises the attributes and properties of the SDP.""" - - # set up the OPC ua client - self.OPCua_client = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), "http://lofar.eu", self.OPC_Time_Out, self.Fault, self) - - # map an access helper class - for i in self.attr_list(): - try: - i.set_comm_client(self.OPCua_client) - except Exception as e: - # use the pass function instead of setting read/write fails - i.set_pass_func() - self.warn_stream("error while setting the SDP attribute {} read/write function. {}".format(i, e)) - pass - - self.OPCua_client.start() - - # -------- - # Commands - # -------- - -# ---------- -# Run server -# ---------- -def main(args=None, **kwargs): - """Main function of the SDP module.""" - - from common.lofar_logging import configure_logger - configure_logger() - - return run((SDP,), args=args, **kwargs) - - -if __name__ == '__main__': - main() diff --git a/devices/devices/sdp/sst.py b/devices/devices/sdp/sst.py deleted file mode 100644 index 1a62a4edcf28c84f7be865d38f7d5312417b497e..0000000000000000000000000000000000000000 --- a/devices/devices/sdp/sst.py +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of the SST project -# -# -# -# Distributed under the terms of the APACHE license. -# See LICENSE.txt for more info. - -""" SST Device Server for LOFAR2.0 - -""" - -# TODO(Corne): Remove sys.path.append hack once packaging is in place! -import os, sys -currentdir = os.path.dirname(os.path.realpath(__file__)) -parentdir = os.path.dirname(currentdir) -parentdir = os.path.dirname(parentdir) -sys.path.append(parentdir) - -# PyTango imports -from tango.server import run -from tango.server import device_property, attribute -from tango import AttrWriteType -# Additional import - -from clients.attribute_wrapper import attribute_wrapper -from clients.opcua_client import OPCUAConnection -from clients.statistics_client import StatisticsClient - -from devices.hardware_device import hardware_device - -from common.lofar_git import get_version -from common.lofar_logging import device_logging_to_python, log_exceptions - -from devices.sdp.statistics import Statistics -from devices.sdp.statistics_collector import SSTCollector - -import numpy - -__all__ = ["SST", "main"] - -class SST(Statistics): - - STATISTICS_COLLECTOR_CLASS = SSTCollector - - # ----------------- - # Device Properties - # ----------------- - - # ---------- - # Attributes - # ---------- - - # FPGA control points for SSTs - FPGA_sst_offload_enable_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["2:FPGA_sst_offload_enable_RW"], datatype=numpy.bool_, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_sst_offload_enable_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["2:FPGA_sst_offload_enable_R"], datatype=numpy.bool_, dims=(16,)) - FPGA_sst_offload_hdr_eth_destination_mac_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["2:FPGA_sst_offload_hdr_eth_destination_mac_RW"], datatype=numpy.str_, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_sst_offload_hdr_eth_destination_mac_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["2:FPGA_sst_offload_hdr_eth_destination_mac_R"], datatype=numpy.str_, dims=(16,)) - FPGA_sst_offload_hdr_ip_destination_address_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["2:FPGA_sst_offload_hdr_ip_destination_address_RW"], datatype=numpy.str_, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_sst_offload_hdr_ip_destination_address_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["2:FPGA_sst_offload_hdr_ip_destination_address_R"], datatype=numpy.str_, dims=(16,)) - FPGA_sst_offload_hdr_udp_destination_port_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["2:FPGA_sst_offload_hdr_udp_destination_port_RW"], datatype=numpy.uint16, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_sst_offload_hdr_udp_destination_port_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["2:FPGA_sst_offload_hdr_udp_destination_port_R"], datatype=numpy.uint16, dims=(16,)) - FPGA_sst_offload_selector_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["2:FPGA_sst_offload_selector_RW"], datatype=numpy.bool_, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_sst_offload_selector_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["2:FPGA_sst_offload_selector_R"], datatype=numpy.bool_, dims=(16,)) - - # number of packets with valid payloads - nof_valid_payloads_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "nof_valid_payloads"}, dims=(SSTCollector.MAX_INPUTS,), datatype=numpy.uint64) - # number of packets with invalid payloads - nof_payload_errors_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "nof_payload_errors"}, dims=(SSTCollector.MAX_INPUTS,), datatype=numpy.uint64) - # latest SSTs - sst_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "sst_values"}, dims=(SSTCollector.MAX_SUBBANDS, SSTCollector.MAX_INPUTS), datatype=numpy.uint64) - # reported timestamp for each row in the latest SSTs - sst_timestamp_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "sst_timestamps"}, dims=(SSTCollector.MAX_INPUTS,), datatype=numpy.uint64) - # integration interval for each row in the latest SSTs - integration_interval_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "integration_intervals"}, dims=(SSTCollector.MAX_INPUTS,), datatype=numpy.float32) - - # -------- - # Overloaded functions - # -------- - - # -------- - # Commands - # -------- - -# ---------- -# Run server -# ---------- -def main(args=None, **kwargs): - """Main function of the SST Device module.""" - - from common.lofar_logging import configure_logger - configure_logger() - - return run((SST,), args=args, **kwargs) - - -if __name__ == '__main__': - main() diff --git a/devices/devices/sdp/statistics.py b/devices/devices/sdp/statistics.py deleted file mode 100644 index 5d10aae8b866acc0b30598856cb63b1ecc6d233a..0000000000000000000000000000000000000000 --- a/devices/devices/sdp/statistics.py +++ /dev/null @@ -1,148 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of the SST project -# -# -# -# Distributed under the terms of the APACHE license. -# See LICENSE.txt for more info. - -""" Base device for Statistics (SST/BST/XST) - -""" - -# TODO(Corne): Remove sys.path.append hack once packaging is in place! -import os, sys -currentdir = os.path.dirname(os.path.realpath(__file__)) -parentdir = os.path.dirname(currentdir) -parentdir = os.path.dirname(parentdir) -sys.path.append(parentdir) - -from abc import ABCMeta, abstractmethod - -# PyTango imports -from tango.server import run -from tango.server import device_property, attribute -from tango import AttrWriteType -# Additional import - -from clients.statistics_client import StatisticsClient -from clients.opcua_client import OPCUAConnection -from clients.attribute_wrapper import attribute_wrapper - -from devices.hardware_device import hardware_device - -from common.lofar_git import get_version -from common.lofar_logging import device_logging_to_python, log_exceptions - -import numpy - -__all__ = ["Statistics"] - -class Statistics(hardware_device, metaclass=ABCMeta): - - # In derived classes, set this to a subclass of StatisticsCollector - @property - @abstractmethod - def STATISTICS_COLLECTOR_CLASS(self): - pass - - # ----------------- - # Device Properties - # ----------------- - - OPC_Server_Name = device_property( - dtype='DevString', - mandatory=True - ) - - OPC_Server_Port = device_property( - dtype='DevULong', - mandatory=True - ) - - OPC_Time_Out = device_property( - dtype='DevDouble', - mandatory=True - ) - - Statistics_Client_Port = device_property( - dtype='DevUShort', - mandatory=True - ) - - # ---------- - # Attributes - # ---------- - - version_R = attribute(dtype = str, access = AttrWriteType.READ, fget = lambda self: get_version()) - - # number of UDP packets that were received - nof_packets_received_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "udp", "parameter": "nof_packets_received"}, datatype=numpy.uint64) - # number of UDP packets that were dropped because we couldn't keep up with processing - nof_packets_dropped_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "udp", "parameter": "nof_packets_dropped"}, datatype=numpy.uint64) - # last packet we processed - last_packet_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "udp", "parameter": "last_packet"}, dims=(9000,), datatype=numpy.uint8) - # when last packet was received - last_packet_timestamp_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "udp", "parameter": "last_packet_timestamp"}, datatype=numpy.uint64) - - # queue fill percentage, as reported by the consumer - queue_fill_percentage_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "queue", "parameter": "fill_percentage"}, datatype=numpy.uint64) - - # number of UDP packets that were processed - nof_packets_processed_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "nof_packets"}, datatype=numpy.uint64) - - # number of invalid (non-SST) packets received - nof_invalid_packets_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "nof_invalid_packets"}, datatype=numpy.uint64) - # last packet that could not be parsed - last_invalid_packet_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "last_invalid_packet"}, dims=(9000,), datatype=numpy.uint8) - - # -------- - # Overloaded functions - # -------- - - def configure_for_off(self): - """ user code here. is called when the state is set to OFF """ - - # Stop keep-alive - try: - self.statistics_client.stop() - except Exception as e: - self.warn_stream("Exception while stopping statistics_client in configure_for_off function: {}. Exception ignored".format(e)) - - try: - self.OPCUA_client.stop() - except Exception as e: - self.warn_stream("Exception while stopping OPC UA connection in configure_for_off function: {}. Exception ignored".format(e)) - - @log_exceptions() - def configure_for_initialise(self): - """ user code here. is called when the sate is set to INIT """ - """Initialises the attributes and properties of the statistics device.""" - - self.statistics_client = StatisticsClient(self.STATISTICS_COLLECTOR_CLASS, "0.0.0.0", self.Statistics_Client_Port, self.Fault, self) - - self.OPCUA_client = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), "http://lofar.eu", self.OPC_Time_Out, self.Fault, self) - - # map an access helper class - for i in self.attr_list(): - try: - if i.comms_id == StatisticsClient: - i.set_comm_client(self.statistics_client) - elif i.comms_id == OPCUAConnection: - i.set_comm_client(self.OPCUA_client) - else: - raise ValueError("Cannot set comm client for attribute {}: Unknown comms_id {}".format(i, i.comms_id)) - except Exception as e: - # use the pass function instead of setting read/write fails - i.set_pass_func() - self.warn_stream("error while setting the sst attribute {} read/write function. {}. using pass function instead".format(i, e)) - pass - - self.statistics_client.start() - - self.OPCUA_client.start() - - # -------- - # Commands - # -------- diff --git a/devices/devices/sdp/statistics_collector.py b/devices/devices/sdp/statistics_collector.py deleted file mode 100644 index f3aac3c1982b03b169eaddedce52b50c939ddc45..0000000000000000000000000000000000000000 --- a/devices/devices/sdp/statistics_collector.py +++ /dev/null @@ -1,137 +0,0 @@ -from queue import Queue -from threading import Thread -import logging -import numpy - -from .statistics_packet import SSTPacket - -logger = logging.getLogger() - -class StatisticsCollector(Thread): - """ Base class to process statistics packets from a queue, asynchronously. """ - - # Maximum number of antenna inputs we support (used to determine array sizes) - MAX_INPUTS = 192 - - # Maximum number of subbands we support (used to determine array sizes) - MAX_SUBBANDS = 512 - - # Maximum time to wait for the Thread to get unstuck, if we want to stop - DISCONNECT_TIMEOUT = 10.0 - - def __init__(self, queue: Queue): - self.queue = queue - self.last_packet = None - - self.parameters = self._default_parameters() - - super().__init__() - self.start() - - def _default_parameters(self): - return { - "nof_packets": numpy.uint64(0), - - # Packet count for packets that could not be parsed - "nof_invalid_packets": numpy.uint64(0), - - # Full contents of the latest packet we deemed invalid. - "last_invalid_packet": numpy.zeros((9000,), dtype=numpy.uint8), - } - - def run(self): - logger.info("Starting statistics thread") - - while True: - self.last_packet = self.queue.get() - - # This is the exception/slow path, but python doesn't allow us to optimise that - if self.last_packet is None: - # None is the magic marker to stop processing - break - - self.parameters["nof_packets"] += numpy.uint64(1) - - try: - self.process_packet(self.last_packet) - except Exception as e: - logger.exception("Could not parse statistics UDP packet") - - self.parameters["last_invalid_packet"] = numpy.frombuffer(self.last_packet, dtype=numpy.uint8) - self.parameters["nof_invalid_packets"] += numpy.uint64(1) - - logger.info("Stopped statistics thread") - - def join(self, timeout=0): - # insert magic marker - self.queue.put(None) - logger.info("Sent shutdown to statistics thread") - - super().join(timeout) - - def disconnect(self): - if not self.is_alive(): - return - - # try to get the thread shutdown, but don't stall forever - self.join(self.DISCONNECT_TIMEOUT) - - if self.is_alive(): - # there is nothing we can do except wait (stall) longer, which could be indefinitely. - logger.error(f"Statistics thread did not shut down after {self.DISCONNECT_TIMEOUT} seconds, just leaving it dangling. Please attach a debugger to thread ID {self.ident}.") - - def process_packet(self, packet): - """ Update any information based on this packet. """ - - raise NotImplementedError - - -class SSTCollector(StatisticsCollector): - """ Class to process SST statistics packets. """ - - # Maximum number of antenna inputs we support (used to determine array sizes) - MAX_INPUTS = 192 - - # Maximum number of subbands we support (used to determine array sizes) - MAX_SUBBANDS = 512 - - def _default_parameters(self): - defaults = super()._default_parameters() - - defaults.update({ - # Number of packets received so far that we could parse correctly and do not have a payload error - "nof_valid_payloads": numpy.zeros((self.MAX_INPUTS,), dtype=numpy.uint64), - - # Packets that reported a payload error - "nof_payload_errors": numpy.zeros((self.MAX_INPUTS,), dtype=numpy.uint64), - - # Last value array we've constructed out of the packets - "sst_values": numpy.zeros((self.MAX_INPUTS, self.MAX_SUBBANDS), dtype=numpy.uint64), - "sst_timestamps": numpy.zeros((self.MAX_INPUTS,), dtype=numpy.float64), - "integration_intervals": numpy.zeros((self.MAX_INPUTS,), dtype=numpy.float32), - }) - - return defaults - - def process_packet(self, packet): - fields = SSTPacket(packet) - - # determine which input this packet contains data for - if fields.signal_input_index >= self.MAX_INPUTS: - # packet describes an input that is out of bounds for us - raise ValueError("Packet describes input %d, but we are limited to describing MAX_INPUTS=%d" % (fields.signal_input_index, self.MAX_INPUTS)) - - input_index = fields.signal_input_index - - if fields.payload_error: - # cannot trust the data if a payload error is reported - self.parameters["nof_payload_errors"][input_index] += numpy.uint64(1) - - # don't raise, as packet is valid - return - - # process the packet - self.parameters["nof_valid_payloads"][input_index] += numpy.uint64(1) - self.parameters["sst_values"][input_index][:fields.nof_statistics_per_packet] = fields.payload - self.parameters["sst_timestamps"][input_index] = numpy.float64(fields.timestamp().timestamp()) - self.parameters["integration_intervals"][input_index] = fields.integration_interval() diff --git a/devices/requirements.txt b/devices/requirements.txt deleted file mode 100644 index 8e11e2f537bf59f3602379c853976696df7524f0..0000000000000000000000000000000000000000 --- a/devices/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -# the order of packages is of significance, because pip processes them in the -# order of appearance. Changing the order has an impact on the overall -# integration process, which may cause wedges in the gate later. - -pbr>=2.0 # Apache-2.0 diff --git a/devices/setup.cfg b/devices/setup.cfg deleted file mode 100644 index 586aa190649d3c54b04ce586cdbaa4565570b1b1..0000000000000000000000000000000000000000 --- a/devices/setup.cfg +++ /dev/null @@ -1,30 +0,0 @@ -[metadata] -name = TangoStationControl -summary = LOFAR 2.0 Station Control -description-file = - README.md -description-content-type = text/x-rst; charset=UTF-8 -author = ASTRON -home-page = https://astron.nl -project_urls = - Bug Tracker = https://support.astron.nl/jira/projects/L2SS/issues/ - Source Code = https://git.astron.nl/lofar2.0/tango -license = Apache-2 -classifier = - Environment :: Console - License :: Apache Software License - Operating System :: POSIX :: Linux - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - -[files] -package_dir=./ - -[entry_points] -console_scripts = - SDP = SDP:main - PCC = PCC:main diff --git a/devices/setup.py b/devices/setup.py deleted file mode 100644 index 4fa0ce44d0caa9b174fc65a699e63b31e43aee9b..0000000000000000000000000000000000000000 --- a/devices/setup.py +++ /dev/null @@ -1,4 +0,0 @@ -import setuptools - -# Requires: setup.cfg -setuptools.setup(setup_requires=['pbr>=2.0.0'], pbr=True) diff --git a/devices/test/common/test_lofar_git.py b/devices/test/common/test_lofar_git.py deleted file mode 100644 index 52a1c7d876fc2827757f082e0f44a0a64b1ffc78..0000000000000000000000000000000000000000 --- a/devices/test/common/test_lofar_git.py +++ /dev/null @@ -1,100 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of the LOFAR 2.0 Station Software -# -# -# -# Distributed under the terms of the APACHE license. -# See LICENSE.txt for more info. - -import git -from unittest import mock - -from common import lofar_git - -from test import base - - -class TestLofarGit(base.TestCase): - - def setUp(self): - super(TestLofarGit, self).setUp() - - # Clear the cache as this function of lofar_git uses LRU decorator - # This is a good demonstration of how unit tests in Python can have - # permanent effects, typically fixtures are needed to restore these. - lofar_git.get_version.cache_clear() - - def test_get_version(self): - """Test if attributes of get_repo are correctly used by get_version""" - - with mock.patch.object(lofar_git, 'get_repo') as m_get_repo: - m_commit = mock.Mock() - m_commit.return_value = "123456" - - m_is_dirty = mock.Mock() - m_is_dirty.return_value = True - - m_head = mock.Mock(is_detached=False) - - m_get_repo.return_value = mock.Mock( - active_branch="main", commit=m_commit, tags=[], - is_dirty=m_is_dirty, head=m_head) - - # No need for special string equal in Python - self.assertEqual("*main [123456]", lofar_git.get_version()) - - def test_get_version_tag(self): - """Test if get_version determines production_ready for tagged commit""" - - with mock.patch.object(lofar_git, 'get_repo') as m_get_repo: - m_commit = mock.Mock() - m_commit.return_value = "123456" - - m_is_dirty = mock.Mock() - m_is_dirty.return_value = False - - m_head = mock.Mock(is_detached=False) - - m_tag = mock.Mock(commit="123456") - m_tag.__str__ = mock.Mock(return_value= "version-1.2") - - m_get_repo.return_value = mock.Mock( - active_branch="main", commit=m_commit, - tags=[m_tag], is_dirty=m_is_dirty, head=m_head) - - self.assertEqual("version-1.2", lofar_git.get_version()) - - @mock.patch.object(lofar_git, 'get_repo') - def test_get_version_tag_dirty(self, m_get_repo): - """Test if get_version determines dirty tagged commit""" - - m_commit = mock.Mock() - m_commit.return_value = "123456" - - m_is_dirty = mock.Mock() - m_is_dirty.return_value = False - - m_head = mock.Mock(is_detached=False) - - m_tag = mock.Mock(commit="123456") - m_tag.__str__ = mock.Mock(return_value= "version-1.2") - - # Now m_get_repo is mocked using a decorator - m_get_repo.return_value = mock.Mock( - active_branch="main", commit=m_commit, - tags=[m_tag], is_dirty=m_is_dirty, head=m_head) - - self.assertEqual("version-1.2", lofar_git.get_version()) - - def test_catch_repo_error(self): - """Test if invalid git directories will raise error""" - - with mock.patch.object(lofar_git, 'get_repo') as m_get_repo: - - # Configure lofar_git.get_repo to raise InvalidGitRepositoryError - m_get_repo.side_effect = git.InvalidGitRepositoryError - - # Test that error is raised by get_version - self.assertRaises( - git.InvalidGitRepositoryError, lofar_git.get_version) diff --git a/devices/test/devices/test_device.py b/devices/test/devices/test_device.py deleted file mode 100644 index f9a72ec88d006450403b6cfc6a0396b842bb36a1..0000000000000000000000000000000000000000 --- a/devices/test/devices/test_device.py +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of the PCC project -# -# -# -# Distributed under the terms of the APACHE license. -# See LICENSE.txt for more info. - -""" test Device Server -""" - -# TODO(Corne): Remove sys.path.append hack once packaging is in place! -import os, sys -currentdir = os.path.dirname(os.path.realpath(__file__)) -parentdir = os.path.dirname(currentdir) -parentdir = os.path.dirname(parentdir) -sys.path.append(parentdir) - -# PyTango imports -from tango.server import run -from tango.server import device_property -from tango import DevState -# Additional import - -from test.clients.test_client import test_client -from clients.attribute_wrapper import * -from devices.hardware_device import * - -__all__ = ["test_device", "main"] - - -class test_device(hardware_device): - # ----------------- - # Device Properties - # ----------------- - - OPC_Server_Name = device_property( - dtype='DevString', - ) - - OPC_Server_Port = device_property( - dtype='DevULong', - ) - - OPC_Time_Out = device_property( - dtype='DevDouble', - ) - - # ---------- - # Attributes - # ---------- - bool_scalar_R = attribute_wrapper(comms_annotation="numpy.bool_ type read scalar", datatype=numpy.bool_) - bool_scalar_RW = attribute_wrapper(comms_annotation="numpy.bool_ type read/write scalar", datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) - - int32_spectrum_R = attribute_wrapper(comms_annotation="numpy.int32 type read spectrum (len = 8)", datatype=numpy.int32, dims=(8,)) - int32_spectrum_RW = attribute_wrapper(comms_annotation="numpy.int32 type read spectrum (len = 8)", datatype=numpy.int32, dims=(8,), - access=AttrWriteType.READ_WRITE) - - double_image_R = attribute_wrapper(comms_annotation="numpy.double type read image (dims = 2x8)", datatype=numpy.double, dims=(2, 8)) - double_image_RW = attribute_wrapper(comms_annotation="numpy.double type read/write image (dims = 8x2)", datatype=numpy.double, dims=(8, 2), - access=AttrWriteType.READ_WRITE) - - int32_scalar_R = attribute_wrapper(comms_annotation="numpy.int32 type read scalar", datatype=numpy.int32) - uint16_spectrum_RW = attribute_wrapper(comms_annotation="numpy.uint16 type read/write spectrum (len = 8)", datatype=numpy.uint16, dims=(8,), - access=AttrWriteType.READ_WRITE) - float32_image_R = attribute_wrapper(comms_annotation="numpy.float32 type read image (dims = 8x2)", datatype=numpy.float32, dims=(8, 2)) - uint8_image_RW = attribute_wrapper(comms_annotation="numpy.uint8 type read/write image (dims = 2x8)", datatype=numpy.uint8, dims=(2, 8), - access=AttrWriteType.READ_WRITE) - - # -------- - # overloaded functions - # -------- - def configure_for_initialise(self): - """ user code here. is called when the sate is set to INIT """ - """Initialises the attributes and properties of the PCC.""" - - self.set_state(DevState.INIT) - - # set up the test client - self.test_client = test_client(self.Fault, self) - - # map an access helper class - for i in self.attr_list(): - i.set_comm_client(self.test_client) - - self.test_client.start() - - -# ---------- -# Run server -# ---------- -def main(args=None, **kwargs): - """Main function of the example module.""" - return run((test_device,), args=args, **kwargs) - - -if __name__ == '__main__': - main() diff --git a/devices/toolkit/archiver.py b/devices/toolkit/archiver.py deleted file mode 100644 index 94ce98ce41cc5983834059cf30e08ff7ebf3a8b5..0000000000000000000000000000000000000000 --- a/devices/toolkit/archiver.py +++ /dev/null @@ -1,168 +0,0 @@ -#! /usr/bin/env python3 - -from clients.attribute_wrapper import attribute_wrapper -from tango import DeviceProxy -from datetime import datetime, timedelta - -from sqlalchemy import create_engine, and_ -from sqlalchemy.orm import sessionmaker -from .archiver_base import * - -class Archiver(): - """ - The Archiver class implements the basic operations to perform attributes archiving - """ - def __init__(self, cm_name: str = 'archiving/hdbpp/confmanager01', es_name: str = 'archiving/hdbpp/eventsubscriber01'): - self.cm_name = cm_name - self.cm = DeviceProxy(cm_name) - self.es_name = es_name - self.es = DeviceProxy(es_name) - - def add_attribute_to_archiver(self, attribute: str, polling_period: float = 1000, event_period: float = 1000, strategy: str = 'ALWAYS'): - """ - Takes as input the attribute name, polling period (ms), event period (ms) and archiving strategy, - and adds the selected attribute to the subscriber's list of archiving attributes. - The ConfigurationManager and EventSubscriber devices must be already up and running. - The archiving-DBMS must be already properly configured. - """ - self.cm.write_attribute('SetAttributeName', attribute) - self.cm.write_attribute('SetArchiver', self.es_name) - self.cm.write_attribute('SetStrategy', strategy) - self.cm.write_attribute('SetPollingPeriod', int(polling_period)) - self.cm.write_attribute('SetPeriodEvent', int(event_period)) - self.cm.AttributeAdd() - - def remove_attribute_from_archiver(self, attribute: str): - """ - Stops the data archiving of the attribute passed as input, and remove it from the subscriber's list. - """ - self.cm.AttributeStop(attribute) - self.cm.AttributeRemove(attribute) - -class Retriever(): - """ - The Retriever class implements retrieve operations on a given DBMS - """ - def __init__(self, cm_name: str = 'archiving/hdbpp/confmanager01'): - self.cm_name = cm_name - self.session = self.connect_to_archiving_db() - - def get_db_credentials(self): - """ - Retrieves the DB credentials from the Tango properties of Configuration Manager - """ - cm = DeviceProxy(self.cm_name) - config_list = cm.get_property('LibConfiguration')['LibConfiguration'] # dictionary {'LibConfiguration': list of strings} - host = str([s for s in config_list if "host" in s][0].split('=')[1]) - dbname = str([s for s in config_list if "dbname" in s][0].split('=')[1]) - port = str([s for s in config_list if "port" in s][0].split('=')[1]) - user = str([s for s in config_list if "user" in s][0].split('=')[1]) - pw = str([s for s in config_list if "password" in s][0].split('=')[1]) - return host,dbname,port,user,pw - - def connect_to_archiving_db(self): - """ - Returns a session to a MySQL DBMS using default credentials. - """ - host,dbname,port,user,pw = self.get_db_credentials() - engine = create_engine('mysql+pymysql://'+user+':'+pw+'@'+host+':'+port+'/'+dbname) - Session = sessionmaker(bind=engine) - return Session() - - def get_all_archived_attributes(self): - """ - Returns a list of the archived attributes in the DB. - """ - attrs = self.session.query(Attribute).order_by(Attribute.att_conf_id).all() - # Returns the representation as set in __repr__ method of the mapper class - return attrs - - def get_archived_attributes_by_device(self,device_fqname: str): - """ - Takes as input the fully-qualified name of a device and returns a list of its archived attributes - """ - try: - [domain, family, member] = device_fqname.split('/') - except: - print("Device name error. Use FQDN - eg: LTS/Device/1") - return - attrs = self.session.query(Attribute).filter(and_(Attribute.domain == domain, Attribute.family == family, \ - Attribute.member == member)).all() - # Returns the representation as set in __repr__ method of the mapper class - return attrs - - def get_attribute_id(self,attribute_fqname: str): - """ - Takes as input the fully-qualified name of an attribute and returns its id. - """ - try: - [domain, family, member, name] = attribute_fqname.split('/') - except: - print("Attribute name error. Use FQDN - eg: LTS/Device/1/Attribute") - return - try: - result = self.session.query(Attribute.att_conf_id).filter(and_(Attribute.domain == domain, Attribute.family == family, \ - Attribute.member == member, Attribute.name == name)).one() - return result[0] - except TypeError: - print("Attribute not found!") - return - - def get_attribute_datatype(self,attribute_fqname: str): - """ - Takes as input the fully-qualified name of an attribute and returns its Data-Type. - Data Type name indicates the type (e.g. string, int, ...) and the read/write property. The name is used - as DB table name suffix in which values are stored. - """ - try: - [domain, family, member, name] = attribute_fqname.split('/') - except: - print("Attribute name error. Use FQDN - eg: LTS/Device/1/Attribute") - return - try: - result = self.session.query(DataType.data_type).join(Attribute,Attribute.att_conf_data_type_id==DataType.att_conf_data_type_id).\ - filter(and_(Attribute.domain == domain, Attribute.family == family, Attribute.member == member, Attribute.name == name)).one() - return result[0] - except TypeError: - print("Attribute not found!") - return - - def get_attribute_value_by_hours(self,attribute_fqname: str, hours: float = 1.0): - """ - Takes as input the attribute fully-qualified name and the number of past hours since the actual time - (e.g. hours=1 retrieves values in the last hour, hours=8.5 retrieves values in the last eight hours and half). - Returns a list of timestamps and a list of values - """ - attr_id = self.get_attribute_id(attribute_fqname) - attr_datatype = self.get_attribute_datatype(attribute_fqname) - attr_table_name = 'att_'+str(attr_datatype) - # Retrieves the class that maps the DB table given the tablename - base_class = get_class_by_tablename(attr_table_name) - # Retrieves the timestamp - time_now = datetime.now() - time_delta = time_now - timedelta(hours=hours) - # Converts the timestamps in the right format for the query - time_now_db = str(time_now.strftime("%Y-%m-%d %X")) - time_delta_db = str(time_delta.strftime("%Y-%m-%d %X")) - result = self.session.query(base_class).\ - join(Attribute,Attribute.att_conf_id==base_class.att_conf_id).\ - filter(and_(Attribute.att_conf_id == attr_id,base_class.data_time >= time_delta_db, \ - base_class.data_time <= time_now_db)).order_by(base_class.data_time).all() - return result - - def get_attribute_value_by_interval(self,attribute_fqname: str, start_time: datetime, stop_time: datetime): - ''' - Takes as input the attribute name and a certain starting and ending point-time. - The datetime format is pretty flexible (e.g. "YYYY-MM-dd hh:mm:ss"). - Returns a list of timestamps and a list of values - ''' - attr_id = self.get_attribute_id(attribute_fqname) - attr_datatype = self.get_attribute_datatype(attribute_fqname) - attr_table_name = 'att_'+str(attr_datatype) - # Retrieves the class that maps the DB table given the tablename - base_class = get_class_by_tablename(attr_table_name) - result = self.session.query(base_class).\ - join(Attribute,Attribute.att_conf_id==base_class.att_conf_id).\ - filter(and_(Attribute.att_conf_id == attr_id,base_class.data_time >= str(start_time), \ - base_class.data_time <= str(stop_time))).order_by(base_class.data_time).all() - return result diff --git a/devices/toolkit/lts_cold_start.py b/devices/toolkit/lts_cold_start.py deleted file mode 100644 index fb558ff2ce849ab9f844331c117aee122af014fe..0000000000000000000000000000000000000000 --- a/devices/toolkit/lts_cold_start.py +++ /dev/null @@ -1,228 +0,0 @@ -#! /usr/bin/env python3 -import logging -from time import sleep - -# TODO(Corne): Remove sys.path.append hack once packaging is in place! -import os, sys -currentdir = os.path.dirname(os.path.realpath(__file__)) -parentdir = os.path.dirname(currentdir) -sys.path.append(parentdir) - -from toolkit.startup import startup -from toolkit.lofar2_config import configure_logging - - -def start_device(device: str): - ''' - Start a Tango device with the help of the startup function. - The device will not be forced to got through - OFF/INIT/STANDBY/ON but it is assumed that the device is in OFF - state. If the device is not in OFF state, then an exception - will be raised. - ''' - dev = startup(device = device, force_restart = False) - state = device.state() - if state is not tango._tango.DevState.ON: - raise Exception("Device \"{}\" is unexpectedly in \"{}\" state but it is expected to be in \"{}\" state. Please check the reason for the unexpected device state. Aborting the start-up procedure.".format(device, state, tango._tango.DevState.ON)) - return device - - -def lts_cold_start(): - ''' - What is this? - This is the LTS (LOFAR Test - and I forgot what S stands for) cold start - procedure cast into source code. The procedure can be found there: - https://support.astron.nl/confluence/display/L2M/LTS+startup+procedure - - Paulus wrote already a script that - illegally ;) - makes direct use of the - OPC-UA servers to accomplish the same thing that we are doing here. - Paulus' script can be found there: - https://git.astron.nl/lofar2.0/pypcc/-/blob/master/scripts/Startup.py - Thanks, Paulus! You made it very easy for me to cobble together this - script. - - For obvious reasons is our script much better though. :) - First, it is bigger. And bigger is always better. - Then it is better documented but that does not count in the HW world. - But it also raises exceptions with error messages that make an attempt to - help the user reading them and shuts down the respective Tango device(s) if - something goes south. - And that is where we try to do it really right: there is no reason to be - excessively verbatim when things work like they are expected to work. But - tell the user when something goes wrong, give an indication of what could - have gone wrong and where to look for the problem. - - Again, Paulus' script contains already very good indications where problems - might lie and made my job very easy. - - No parameters, parameters are for wimps. :) - ''' - # Define the LOFAR2.0 specific log format - configure_logging() - - # Get a reference to the PCC device, do not - # force a restart of the already running Tango - # device. - pcc = startup("LTS/PCC/1") - - # Getting CLK, RCU & RCU ADCs into proper shape for use by real people. - # - # The start-up needs to happen in this sequence due to HW dependencies - # that can introduce issues which are then becoming very complicated to - # handle in SW. Therefore to keep it as simple as possible, let's stick - # to the rule recommended by Paulus: - # 1 CLK - # 2 RCU - # 3 RCU ADCs - # - # - # First take the CLK board through the motions. - # 1.1 Switch off CLK - # 1.2 Wait for CLK_translator_busy_R == True, throw an exception in timeout - # 1.3 Switch on CLK - # 1.4 Wait for CLK_translator_busy_R == True, throw an exception in timeout - # 1.5 Check if CLK_PLL_locked_R == True - # 1.6 Done - # - # - # Steps 1.1 & 1.2 - pcc.CLK_off() - # 2021-04-30, Thomas - # This should be refactored into a function. - timeout = 10.0 - while pcc.CLK_translator_busy_R is True: - logging.debug("Waiting on \"CLK_translator_busy_R\" to become \"True\"...") - timeout = timeout - 1.0 - if timeout < 1.0: - # Switching the PCC clock off should never take longer than - # 10 seconds. Here we ran into a timeout. - # Clean up and raise an exception. - pcc.off() - raise Exception("After calling \"CLK_off\" a timeout occured while waiting for \"CLK_translator_busy_R\" to become \"True\". Please investigate the reason why the PCC translator never set \"CLK_translator_busy_R\" to \"True\". Aborting start-up procedure.") - sleep(1.0) - - # Steps 1.3 & 1.4 - pcc.CLK_on() - # Per Paulus this should never take longer than 2 seconds. - # 2021-04-30, Thomas - # This should be refactored into a function. - timeout = 2.0 - while pcc.CLK_translator_busy_R is True: - logging.debug("After calling \"CLK_on()\" Waiting on \"CLK_translator_busy_R\" to become \"True\"...") - timeout = timeout - 1.0 - if timeout < 1.0: - # Switching the PCC clock on should never take longer than - # a couple of seconds. Here we ran into a timeout. - # Clean up and raise an exception. - pcc.off() - raise Exception("After calling \"CLK_on\" a timeout occured while waiting for \"CLK_translator_busy_R\" to become \"True\". Please investigate the reason why the PCC translator never set \"CLK_translator_busy_R\" to \"True\". Aborting start-up procedure.") - sleep(1.0) - - # 1.5 Check if CLK_PLL_locked_R == True - # 2021-04-30, Thomas - # This should be refactored into a function. - clk_locked = pcc.CLK_PLL_locked_R - if clk_locked is True: - logging.info("CLK signal is locked.") - else: - # CLK signal is not locked - clk_i2c_status = pcc.CLK_I2C_STATUS_R - exception_text = "CLK I2C is not working. Please investigate! Maybe power cycle subrack to restart CLK board and translator. Aborting start-up procedure." - if i2c_status <= 0: - exception_text = "CLK signal is not locked. Please investigate! The subrack probably do not receive clock input or the CLK PCB is broken. Aborting start-up procedure." - pcc.off() - raise Exception(exception_text) - # Step 1.6 - # Done. - - # 2 RCUs - # If we reach this point in the start-up procedure, then the CLK board setup - # is done. We can proceed with the RCUs. - # - # Now take the RCUs through the motions. - # 2.1 Set RCU mask to all available RCUs - # 2.2 Switch off all RCUs - # 2.3 Wait for RCU_translator_busy_R = True, throw an exception in timeout - # 2.4 Switch on RCUs - # 2.5 Wait for RCU_translator_busy_R = True, throw an exception in timeout - # 2.6 Done - # - # - # Step 2.1 - # We have only 8 RCUs in LTS. - pcc.RCU_mask_RW = [True, ] * 8 - # Steps 2.2 & 2.3 - pcc.RCU_off() - # 2021-04-30, Thomas - # This should be refactored into a function. - timeout = 10.0 - while pcc.RCU_translator_busy_R is True: - logging.debug("Waiting on \"RCU_translator_busy_R\" to become \"True\"...") - timeout = timeout - 1.0 - if timeout < 1.0: - # Switching the RCUs off should never take longer than - # 10 seconds. Here we ran into a timeout. - # Clean up and raise an exception. - pcc.off() - raise Exception("After calling \"RCU_off\" a timeout occured while waiting for \"RCU_translator_busy_R\" to become \"True\". Please investigate the reason why the PCC translator never set \"RCU_translator_busy_R\" to \"True\". Aborting start-up procedure.") - sleep(1.0) - - # Steps 2.4 & 2.5 - # We leave the RCU mask as it is because it got already set for the - # RCU_off() call. - pcc.RCU_on() - # Per Paulus this should never take longer than 5 seconds. - # 2021-04-30, Thomas - # This should be refactored into a function. - timeout = 5.0 - while pcc.RCU_translator_busy_R is True: - logging.debug("After calling \"RCU_on()\" Waiting on \"RCU_translator_busy_R\" to become \"True\"...") - timeout = timeout - 1.0 - if timeout < 1.0: - # Switching the RCUs on should never take longer than - # a couple of seconds. Here we ran into a timeout. - # Clean up and raise an exception. - pcc.off() - raise Exception("After calling \"RCU_on\" a timeout occured while waiting for \"RCU_translator_busy_R\" to become \"True\". Please investigate the reason why the PCC translator never set \"RCU_translator_busy_R\" to \"True\". Aborting start-up procedure.") - sleep(1.0) - # Step 2.6 - # Done. - - # 3 ADCs - # If we get here, we only got to check if the ADCs are locked, too. - # 3.1 Check RCUs' I2C status - # 3.2 Check RCU_ADC_lock_R == [True, ] for RCUs that have a good I2C status - # 3.3 Done - # - # - # Steps 3.1 & 3.2 - rcu_mask = pcc.RCU_mask_RW - adc_locked = numpy.array(pcc.RCU_ADC_lock_R) - for rcu, i2c_status in enumerate(pcc.RCU_I2C_STATUS_R): - if i2c_status == 0: - rcu_mask[rcu] = True - logging.info("RCU #{} is available.".format(rcu)) - for adc, adc_is_locked in enumerate(adc_locked[rcu]): - if adc_is_locked < 1: - logging.warning("RCU#{}, ADC#{} is unlocked. Please investigate! Will continue with normal operation.".format(rcu, adc)) - else: - # The RCU's I2C bus is not working. - rcu_mask[rcu] = False - logging.error("RCU #{}'s I2C is not working. Please investigate! Disabling RCU #{} to avoid damage.".format(rcu, rcu)) - pcc.RCU_mask_RW = rcu_mask - # Step 3.3 - # Done - - # Start-up APSCTL, i.e. Uniboard2s. - aps = startup("APSCTL/SDP/1") - logging.warning("Cannot start-up APSCTL because it requires manual actions.") - - # Start up SDP, i.e. configure the firmware in the Unibards - sdp = startup("LTS/SDP/1") - logging.warning("Cannot start-up SDP because it requires manual actions.") - - logging.info("LTS has been successfully started and configured.") - - -if __name__ == '__main__': - lts_cold_start() diff --git a/devices/toolkit/startup.py b/devices/toolkit/startup.py deleted file mode 100644 index 0f4bcbe702b1bd1edb873234763d56455b6009b4..0000000000000000000000000000000000000000 --- a/devices/toolkit/startup.py +++ /dev/null @@ -1,36 +0,0 @@ -#! /usr/bin/env python3 - - -def startup(device: str, force_restart: bool): - ''' - Start a LOFAR Tango device: - pcc = startup(device = 'LTS/PCC/1', force_restart = False) - ''' - import tango - proxy = tango.DeviceProxy(device) - state = proxy.state() - - if force_restart is True: - print("Forcing device {} restart.".format(device)) - proxy.off() - state = proxy.state() - if state is not tango._tango.DevState.OFF: - print("Device {} cannot perform off although restart has been enforced, state = {}. Please investigate.".format(device, state)) - return proxy - if state is not tango._tango.DevState.OFF: - print("Device {} is not in OFF state, cannot start it. state = {}".format(device, state)) - return proxy - print("Device {} is in OFF, performing initialisation.".format(device)) - proxy.initialise() - state = proxy.state() - if state is not tango._tango.DevState.STANDBY: - print("Device {} cannot perform initialise, state = {}. Please investigate.".format(device, state)) - return proxy - print("Device {} is in STANDBY, performing on.".format(device)) - proxy.on() - state = proxy.state() - if state is not tango._tango.DevState.ON: - print("Device {} cannot perform on, state = {}. Please investigate.".format(device, state)) - else: - print("Device {} has successfully reached ON state.".format(device)) - return proxy diff --git a/docker-compose/.env b/docker-compose/.env index 85ebd21e4cfc0392b1f3f4452f22c861f1d304fe..00b12b0fcb55b351ebf9e56b37ae2c1f0fc6f4a9 100644 --- a/docker-compose/.env +++ b/docker-compose/.env @@ -8,7 +8,7 @@ TANGO_CPP_VERSION=9.3.5 TANGO_DB_VERSION=10.4.11 TANGO_DSCONFIG_VERSION=1.5.1 TANGO_HDBPP_VIEWER_VERSION=2021-05-28 -TANGO_ITANGO_VERSION=9.3.5 +TANGO_ITANGO_VERSION=9.3.7 TANGO_JAVA_VERSION=9.3.4 TANGO_POGO_VERSION=9.6.32 TANGO_REST_VERSION=1.14.2 diff --git a/docker-compose/Makefile b/docker-compose/Makefile index 09eb760123bc4687207609c3ad94c740a72c317c..d85ff1df88d91db097bdd22b060cfc03b681a04f 100644 --- a/docker-compose/Makefile +++ b/docker-compose/Makefile @@ -1,11 +1,3 @@ -# Before doing anything, make 100% certain that all necessary environment -# variables are set. -ifndef LOFAR20_DIR - # Generate the full path to the lofar20rc.sh file - # for exactly this repository. - LOFAR20RC = $(subst docker-compose,bootstrap/etc/lofar20rc.sh,$(abspath .)) - $(error There are essential LOFAR2.0 environment variables missing. You must source the lofar20rc.sh file of this repo first. You can do it like this (paste without quotes): ". $(LOFAR20RC)") -endif # Set dir of Makefile to a variable to use later MAKEPATH := $(abspath $(lastword $(MAKEFILE_LIST))) BASEDIR := $(notdir $(patsubst %/,%,$(dir $(MAKEPATH)))) @@ -21,14 +13,30 @@ ATTACH_COMPOSE_FILE_ARGS := $(foreach yml,$(filter-out tango.yml,$(COMPOSE_FILES # But we allow to overwrite it. NETWORK_MODE ?= tangonet -# Host name through which others can reach our control interfaces -HOSTNAME ?= $(shell hostname -f) +# Host name through which others can reach our control interfaces. +# Needs to be resolvable from the containers and clients. +ifneq (,$(wildcard /run/WSL)) + # Microsoft Windows Subsystem for Linux + HOSTNAME ?= host.docker.internal +else + HOSTNAME ?= $(shell hostname -f) +endif + +# Host name to which to send our container logs. Needs to be resolvable from +# the host. +LOG_HOSTNAME ?= localhost # If the first make argument is "start" or "stop"... ifeq (start,$(firstword $(MAKECMDGOALS))) SERVICE_TARGET = true else ifeq (stop,$(firstword $(MAKECMDGOALS))) SERVICE_TARGET = true +else ifeq (restart,$(firstword $(MAKECMDGOALS))) + SERVICE_TARGET = true +else ifeq (build,$(firstword $(MAKECMDGOALS))) + SERVICE_TARGET = true +else ifeq (build-nocache,$(firstword $(MAKECMDGOALS))) + SERVICE_TARGET = true else ifeq (attach,$(firstword $(MAKECMDGOALS))) SERVICE_TARGET = true ifndef NETWORK_MODE @@ -108,17 +116,18 @@ endif DOCKER_COMPOSE_ARGS := DISPLAY=$(DISPLAY) \ XAUTHORITY=$(XAUTHORITY) \ TANGO_HOST=$(TANGO_HOST) \ + MYSQL_HOST=$(MYSQL_HOST) \ HOSTNAME=$(HOSTNAME) \ + LOG_HOSTNAME=$(LOG_HOSTNAME) \ NETWORK_MODE=$(NETWORK_MODE) \ XAUTHORITY_MOUNT=$(XAUTHORITY_MOUNT) \ - TANGO_LOFAR_CONTAINER_MOUNT=$(TANGO_LOFAR_CONTAINER_MOUNT) \ - TANGO_LOFAR_CONTAINER_DIR=${TANGO_LOFAR_CONTAINER_DIR} MYSQL_HOST=$(MYSQL_HOST) \ CONTAINER_NAME_PREFIX=$(CONTAINER_NAME_PREFIX) \ COMPOSE_IGNORE_ORPHANS=true \ - CONTAINER_EXECUTION_UID=$(shell id -u) + CONTAINER_EXECUTION_UID=$(shell id -u) \ + DOCKER_GID=$(shell getent group docker | cut -d: -f 3) -.PHONY: up down minimal start stop status clean pull help +.PHONY: up down minimal start stop restart build build-nocache status clean pull help .DEFAULT_GOAL := help pull: ## pull the images from the Docker hub @@ -126,11 +135,16 @@ pull: ## pull the images from the Docker hub build: ## rebuild images # docker-compose does not support build dependencies, so manage those here - $(DOCKER_COMPOSE_ARGS) docker-compose -f lofar-device-base.yml -f networks.yml build - $(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) build + $(DOCKER_COMPOSE_ARGS) docker-compose -f lofar-device-base.yml -f networks.yml build --progress=plain + $(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) build --parallel --progress=plain $(SERVICE) + +build-nocache: ## rebuild images from scratch + # docker-compose does not support build dependencies, so manage those here + $(DOCKER_COMPOSE_ARGS) docker-compose -f lofar-device-base.yml -f networks.yml build --progress=plain + $(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) build --no-cache --progress=plain $(SERVICE) up: minimal ## start the base TANGO system and prepare all services - $(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) up --no-start + $(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) up --no-start --no-recreate down: ## stop all services and tear down the system $(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) down @@ -146,12 +160,24 @@ ifneq ($(NETWORK_MODE),host) endif $(DOCKER_COMPOSE_ARGS) docker-compose -f tango.yml -f networks.yml up -d +bootstrap: pull build # first start, initialise from scratch + $(MAKE) start elk-configure-host # configure host kernel for elk container + $(MAKE) start dsconfig # boot up containers to load configurations + sleep 5 # wait for dsconfig container to come up + ../sbin/update_ConfigDb.sh ../CDB/LOFAR_ConfigDb.json # load default configuration + ../sbin/update_ConfigDb.sh ../CDB/stations/simulators_ConfigDb.json # by default, use simulators + start: up ## start a service (usage: make start <servicename>) - if [ $(UNAME_S) = Linux ]; then chmod a+r ~/.Xauthority; fi + if [ $(UNAME_S) = Linux ]; then touch ~/.Xauthority; chmod a+r ~/.Xauthority; fi $(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) start $(SERVICE) stop: ## stop a service (usage: make stop <servicename>) $(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) stop $(SERVICE) + $(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) rm -f $(SERVICE) + +restart: ## restart a service (usage: make restart <servicename>) + make stop $(SERVICE) # cannot use dependencies, as that would allow start and stop to run in parallel.. + make start $(SERVICE) attach: ## attach a service to an existing Tango network $(DOCKER_COMPOSE_ARGS) docker-compose $(ATTACH_COMPOSE_FILE_ARGS) up -d $(SERVICE) @@ -162,8 +188,9 @@ status: ## show the container status images: ## show the container images $(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) images -clean: down ## clear all TANGO database entries +clean: down ## clear all TANGO database entries, and all containers docker volume rm $(BASEDIR)_tangodb + $(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) rm -f help: ## show this help. @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/docker-compose/apsct-sim.yml b/docker-compose/apsct-sim.yml new file mode 100644 index 0000000000000000000000000000000000000000..d30f5a026f734bb72ee91c7bf533df677f37ca88 --- /dev/null +++ b/docker-compose/apsct-sim.yml @@ -0,0 +1,17 @@ +# +# Docker compose file that launches an APSCT simulator +# +# Defines: +# - apsct-sim +# +version: '2' + +services: + apsct-sim: + build: + context: pypcc-sim-base + container_name: ${CONTAINER_NAME_PREFIX}apsct-sim + networks: + - control + entrypoint: python3 pypcc2.py --simulator --port 4843 --config APSCTTR + restart: on-failure diff --git a/docker-compose/apspu-sim.yml b/docker-compose/apspu-sim.yml new file mode 100644 index 0000000000000000000000000000000000000000..d3fc5fa04f6ce0d6ddfe4c8f87887ab7500720e3 --- /dev/null +++ b/docker-compose/apspu-sim.yml @@ -0,0 +1,17 @@ +# +# Docker compose file that launches an APSPU simulator +# +# Defines: +# - apspu-sim +# +version: '2' + +services: + apspu-sim: + build: + context: pypcc-sim-base + container_name: ${CONTAINER_NAME_PREFIX}apspu-sim + networks: + - control + entrypoint: python3 pypcc2.py --simulator --port 4842 --config APSPUTR + restart: on-failure diff --git a/docker-compose/archiver.yml b/docker-compose/archiver.yml index 41d5df160e011ca1aad79828bf2fd3c941958620..1a56516c707ba965486432e753e45e24c14cbdc0 100644 --- a/docker-compose/archiver.yml +++ b/docker-compose/archiver.yml @@ -3,9 +3,11 @@ version: '2' services: archiver-maria-db: image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/mariadb_hdbpp:2021-05-28 - container_name: archiver-maria-db + container_name: ${CONTAINER_NAME_PREFIX}archiver-maria-db networks: - control + ports: + - "3307:3306/tcp" depends_on: - databaseds environment: @@ -15,13 +17,19 @@ services: - MYSQL_USER=tango - MYSQL_PASSWORD=tango - TANGO_HOST=${TANGO_HOST} - restart: on-failure + logging: + driver: syslog + options: + syslog-address: udp://${LOG_HOSTNAME}:1514 + syslog-format: rfc3164 + tag: "{{.Name}}" + restart: unless-stopped hdbpp-es: image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-archiver:2021-05-28 networks: - control - container_name: hdbpp-es + container_name: ${CONTAINER_NAME_PREFIX}hdbpp-es depends_on: - databaseds - dsconfig @@ -34,12 +42,19 @@ services: wait-for-it.sh archiver-maria-db:3306 --timeout=30 --strict -- wait-for-it.sh ${TANGO_HOST} --timeout=30 --strict -- hdbppes-srv 01" + logging: + driver: syslog + options: + syslog-address: udp://${LOG_HOSTNAME}:1514 + syslog-format: rfc3164 + tag: "{{.Name}}" + restart: unless-stopped hdbpp-cm: image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-archiver:${TANGO_ARCHIVER_VERSION} networks: - control - container_name: hdbpp-cm + container_name: ${CONTAINER_NAME_PREFIX}hdbpp-cm depends_on: - databaseds - dsconfig @@ -52,10 +67,16 @@ services: wait-for-it.sh archiver-maria-db:3306 --timeout=30 --strict -- wait-for-it.sh ${TANGO_HOST} --timeout=30 --strict -- hdbppcm-srv 01" + logging: + driver: syslog + options: + syslog-address: udp://${LOG_HOSTNAME}:1514 + syslog-format: rfc3164 + tag: "{{.Name}}" dsconfig: image: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-dsconfig:${TANGO_DSCONFIG_VERSION} - container_name: dsconfig + container_name: ${CONTAINER_NAME_PREFIX}dsconfig networks: - control depends_on: @@ -67,8 +88,14 @@ services: json2tango -w -a -u /tango-archiver/data/archiver-devices.json && sleep infinity" volumes: - - ${TANGO_LOFAR_CONTAINER_MOUNT} + - ..:/opt/lofar/tango:rw - ${HOME}:/hosthome - ../docker/tango/tango-archiver:/tango-archiver - restart: on-failure + logging: + driver: syslog + options: + syslog-address: udp://${LOG_HOSTNAME}:1514 + syslog-format: rfc3164 + tag: "{{.Name}}" + restart: unless-stopped diff --git a/docker-compose/device-pcc.yml b/docker-compose/device-apsct.yml similarity index 67% rename from docker-compose/device-pcc.yml rename to docker-compose/device-apsct.yml index ebf71352df76969e879a5d73f022705a202ab925..60f65fc47ed81822242282fc743846221acec2d9 100644 --- a/docker-compose/device-pcc.yml +++ b/docker-compose/device-apsct.yml @@ -13,30 +13,29 @@ version: '2' services: - device-pcc: - image: device-pcc + device-apsct: + image: device-apsct # build explicitly, as docker-compose does not understand a local image # being shared among services. build: context: lofar-device-base args: SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION} - container_name: ${CONTAINER_NAME_PREFIX}device-pcc + container_name: ${CONTAINER_NAME_PREFIX}device-apsct networks: - control ports: - - "5700:5700" # unique port for this DS + - "5709:5709" # unique port for this DS + extra_hosts: + - "host.docker.internal:host-gateway" volumes: - - ${TANGO_LOFAR_CONTAINER_MOUNT} + - ..:/opt/lofar/tango:rw environment: - TANGO_HOST=${TANGO_HOST} + working_dir: /opt/lofar/tango entrypoint: - - /usr/local/bin/wait-for-it.sh - - ${TANGO_HOST} - - --timeout=30 - - --strict - - -- + - bin/start-ds.sh # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA # can't know about our Docker port forwarding - - python3 -u ${TANGO_LOFAR_CONTAINER_DIR}/devices/devices/pcc.py LTS -v -ORBendPoint giop:tcp:0:5700 -ORBendPointPublish giop:tcp:${HOSTNAME}:5700 + - l2ss-apsct Apsct STAT -v -v -ORBendPoint giop:tcp:0:5709 -ORBendPointPublish giop:tcp:${HOSTNAME}:5709 restart: on-failure diff --git a/docker-compose/device-apspu.yml b/docker-compose/device-apspu.yml new file mode 100644 index 0000000000000000000000000000000000000000..b694b09518215e293d19e1ff551f4f608e6f818d --- /dev/null +++ b/docker-compose/device-apspu.yml @@ -0,0 +1,41 @@ +# +# Docker compose file that launches an interactive iTango session. +# +# Connect to the interactive session with 'docker attach itango'. +# Disconnect with the Docker deattach sequence: <CTRL>+<P> <CTRL>+<Q> +# +# Defines: +# - itango: iTango interactive session +# +# Requires: +# - lofar-device-base.yml +# +version: '2' + +services: + device-apspu: + image: device-apspu + # build explicitly, as docker-compose does not understand a local image + # being shared among services. + build: + context: lofar-device-base + args: + SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION} + container_name: ${CONTAINER_NAME_PREFIX}device-apspu + networks: + - control + ports: + - "5710:5710" # unique port for this DS + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ..:/opt/lofar/tango:rw + environment: + - TANGO_HOST=${TANGO_HOST} + working_dir: /opt/lofar/tango + entrypoint: + - bin/start-ds.sh + # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA + # can't know about our Docker port forwarding + - l2ss-apspu Apspu STAT -v -ORBendPoint giop:tcp:0:5710 -ORBendPointPublish giop:tcp:${HOSTNAME}:5710 + restart: on-failure diff --git a/docker-compose/device-boot.yml b/docker-compose/device-boot.yml new file mode 100644 index 0000000000000000000000000000000000000000..3db111410fafde9901fd8f91cb40a1c3560e4242 --- /dev/null +++ b/docker-compose/device-boot.yml @@ -0,0 +1,40 @@ +# +# Docker compose file that launches a LOFAR2.0 station's +# ObservationControl device. It also runs the dynamically +# created Observation devices. +# +# Defines: +# - device-observation_control: LOFAR2.0 station ObvservationControl +# +# Requires: +# - lofar-device-base.yml +# +version: '2' + +services: + device-boot: + image: device-boot + # build explicitly, as docker-compose does not understand a local image + # being shared among services. + build: + context: lofar-device-base + args: + SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION} + container_name: ${CONTAINER_NAME_PREFIX}device-boot + networks: + - control + ports: + - "5708:5708" # unique port for this DS + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ..:/opt/lofar/tango:rw + environment: + - TANGO_HOST=${TANGO_HOST} + working_dir: /opt/lofar/tango + entrypoint: + - bin/start-ds.sh + # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA + # can't know about our Docker port forwarding + - l2ss-boot Boot STAT -v -ORBendPoint giop:tcp:0:5708 -ORBendPointPublish giop:tcp:${HOSTNAME}:5708 + restart: unless-stopped diff --git a/docker-compose/device-docker.yml b/docker-compose/device-docker.yml new file mode 100644 index 0000000000000000000000000000000000000000..5a2641e9871f163f27ed7a60d872d30d4fe855e1 --- /dev/null +++ b/docker-compose/device-docker.yml @@ -0,0 +1,43 @@ +# +# Docker compose file that launches an interactive iTango session. +# +# Connect to the interactive session with 'docker attach itango'. +# Disconnect with the Docker deattach sequence: <CTRL>+<P> <CTRL>+<Q> +# +# Defines: +# - itango: iTango interactive session +# +# Requires: +# - lofar-device-base.yml +# +version: '2' + +services: + device-docker: + image: device-docker + # build explicitly, as docker-compose does not understand a local image + # being shared among services. + build: + context: lofar-device-base + args: + SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION} + container_name: ${CONTAINER_NAME_PREFIX}device-docker + networks: + - control + ports: + - "5705:5705" # unique port for this DS + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ..:/opt/lofar/tango:rw + - /var/run/docker.sock:/var/run/docker.sock:rw # we want to control our sibling containers, NOT do docker-in-docker (dind) + user: 1000:${DOCKER_GID} # uid 1000 is the default "tango" user + environment: + - TANGO_HOST=${TANGO_HOST} + working_dir: /opt/lofar/tango + entrypoint: + - bin/start-ds.sh + # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA + # can't know about our Docker port forwarding + - l2ss-docker-device Docker STAT -v -ORBendPoint giop:tcp:0:5705 -ORBendPointPublish giop:tcp:${HOSTNAME}:5705 + restart: unless-stopped diff --git a/docker-compose/device-observation_control.yml b/docker-compose/device-observation_control.yml index c3cbb19d6bcf331b9ce96fccb74c9d0d6f76b758..33fb0d066fd76b8eb4a9c7753266f16d04157726 100644 --- a/docker-compose/device-observation_control.yml +++ b/docker-compose/device-observation_control.yml @@ -25,17 +25,16 @@ services: - control ports: - "5703:5703" # unique port for this DS + extra_hosts: + - "host.docker.internal:host-gateway" volumes: - - ${TANGO_LOFAR_CONTAINER_MOUNT} + - ..:/opt/lofar/tango:rw environment: - TANGO_HOST=${TANGO_HOST} + working_dir: /opt/lofar/tango entrypoint: - - /usr/local/bin/wait-for-it.sh - - ${TANGO_HOST} - - --timeout=30 - - --strict - - -- + - bin/start-ds.sh # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA # can't know about our Docker port forwarding - - python3 -u ${TANGO_LOFAR_CONTAINER_DIR}/devices/devices/observation_control.py LTS -v -ORBendPoint giop:tcp:0:5703 -ORBendPointPublish giop:tcp:${HOSTNAME}:5703 - restart: on-failure + - l2ss-observation-control ObservationControl STAT -v -ORBendPoint giop:tcp:0:5703 -ORBendPointPublish giop:tcp:${HOSTNAME}:5703 + restart: unless-stopped diff --git a/docker-compose/device-recv.yml b/docker-compose/device-recv.yml new file mode 100644 index 0000000000000000000000000000000000000000..a08f566e7b39e095403f00cb5b086420b689d66b --- /dev/null +++ b/docker-compose/device-recv.yml @@ -0,0 +1,41 @@ +# +# Docker compose file that launches an interactive iTango session. +# +# Connect to the interactive session with 'docker attach itango'. +# Disconnect with the Docker deattach sequence: <CTRL>+<P> <CTRL>+<Q> +# +# Defines: +# - itango: iTango interactive session +# +# Requires: +# - lofar-device-base.yml +# +version: '2' + +services: + device-recv: + image: device-recv + # build explicitly, as docker-compose does not understand a local image + # being shared among services. + build: + context: lofar-device-base + args: + SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION} + container_name: ${CONTAINER_NAME_PREFIX}device-recv + networks: + - control + ports: + - "5707:5707" # unique port for this DS + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ..:/opt/lofar/tango:rw + environment: + - TANGO_HOST=${TANGO_HOST} + working_dir: /opt/lofar/tango + entrypoint: + - bin/start-ds.sh + # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA + # can't know about our Docker port forwarding + - l2ss-receiver RECV STAT -v -ORBendPoint giop:tcp:0:5707 -ORBendPointPublish giop:tcp:${HOSTNAME}:5707 + restart: unless-stopped diff --git a/docker-compose/device-sdp.yml b/docker-compose/device-sdp.yml index a65bb8ae3e3111ad6c2954b44b9c20a6e8085321..f32c34394475c6a7483cb98cd03def1f62cf9ff0 100644 --- a/docker-compose/device-sdp.yml +++ b/docker-compose/device-sdp.yml @@ -26,17 +26,16 @@ services: - control ports: - "5701:5701" # unique port for this DS + extra_hosts: + - "host.docker.internal:host-gateway" volumes: - - ${TANGO_LOFAR_CONTAINER_MOUNT} + - ..:/opt/lofar/tango:rw environment: - TANGO_HOST=${TANGO_HOST} + working_dir: /opt/lofar/tango entrypoint: - - /usr/local/bin/wait-for-it.sh - - ${TANGO_HOST} - - --timeout=30 - - --strict - - -- + - bin/start-ds.sh # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA # can't know about our Docker port forwarding - - python3 -u ${TANGO_LOFAR_CONTAINER_DIR}/devices/devices/sdp/sdp.py LTS -v -ORBendPoint giop:tcp:0:5701 -ORBendPointPublish giop:tcp:${HOSTNAME}:5701 - restart: on-failure + - l2ss-sdp SDP STAT -v -ORBendPoint giop:tcp:0:5701 -ORBendPointPublish giop:tcp:${HOSTNAME}:5701 + restart: unless-stopped diff --git a/docker-compose/device-sst.yml b/docker-compose/device-sst.yml index c620ba206b6091b1544582e62128575fc231b03c..7464cb01f45e584ab705fe9098e0229a1b762295 100644 --- a/docker-compose/device-sst.yml +++ b/docker-compose/device-sst.yml @@ -27,18 +27,18 @@ services: - data ports: - "5001:5001/udp" # port to receive SST UDP packets on + - "5101:5101/tcp" # port to emit SST TCP packets on - "5702:5702" # unique port for this DS + extra_hosts: + - "host.docker.internal:host-gateway" volumes: - - ${TANGO_LOFAR_CONTAINER_MOUNT} + - ..:/opt/lofar/tango:rw environment: - TANGO_HOST=${TANGO_HOST} + working_dir: /opt/lofar/tango entrypoint: - - /usr/local/bin/wait-for-it.sh - - ${TANGO_HOST} - - --timeout=30 - - --strict - - -- + - bin/start-ds.sh # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA # can't know about our Docker port forwarding - - python3 -u ${TANGO_LOFAR_CONTAINER_DIR}/devices/devices/sdp/sst.py LTS -v -ORBendPoint giop:tcp:0:5702 -ORBendPointPublish giop:tcp:${HOSTNAME}:5702 - restart: on-failure + - l2ss-sst SST STAT -v -ORBendPoint giop:tcp:0:5702 -ORBendPointPublish giop:tcp:${HOSTNAME}:5702 + restart: unless-stopped diff --git a/docker-compose/device-unb2.yml b/docker-compose/device-unb2.yml new file mode 100644 index 0000000000000000000000000000000000000000..af1329d21a905f3c150c092529978e17f0c0ee37 --- /dev/null +++ b/docker-compose/device-unb2.yml @@ -0,0 +1,41 @@ +# +# Docker compose file that launches an interactive iTango session. +# +# Connect to the interactive session with 'docker attach itango'. +# Disconnect with the Docker deattach sequence: <CTRL>+<P> <CTRL>+<Q> +# +# Defines: +# - itango: iTango interactive session +# +# Requires: +# - lofar-device-base.yml +# +version: '2' + +services: + device-unb2: + image: device-unb2 + # build explicitly, as docker-compose does not understand a local image + # being shared among services. + build: + context: lofar-device-base + args: + SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION} + container_name: ${CONTAINER_NAME_PREFIX}device-unb2 + networks: + - control + ports: + - "5704:5704" # unique port for this DS + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ..:/opt/lofar/tango:rw + environment: + - TANGO_HOST=${TANGO_HOST} + working_dir: /opt/lofar/tango + entrypoint: + - bin/start-ds.sh + # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA + # can't know about our Docker port forwarding + - l2ss-unb2 UNB2 STAT -v -ORBendPoint giop:tcp:0:5704 -ORBendPointPublish giop:tcp:${HOSTNAME}:5704 + restart: unless-stopped diff --git a/docker-compose/device-xst.yml b/docker-compose/device-xst.yml new file mode 100644 index 0000000000000000000000000000000000000000..c4ea684fd94e34fcaaa857a5717ca47745eccc72 --- /dev/null +++ b/docker-compose/device-xst.yml @@ -0,0 +1,44 @@ +# +# Docker compose file that launches an interactive iTango session. +# +# Connect to the interactive session with 'docker attach itango'. +# Disconnect with the Docker deattach sequence: <CTRL>+<P> <CTRL>+<Q> +# +# Defines: +# - itango: iTango interactive session +# +# Requires: +# - lofar-device-base.yml +# +version: '2' + +services: + device-xst: + image: device-xst + # build explicitly, as docker-compose does not understand a local image + # being shared among services. + build: + context: lofar-device-base + args: + SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION} + container_name: ${CONTAINER_NAME_PREFIX}device-xst + networks: + - control + - data + ports: + - "5002:5002/udp" # port to receive XST UDP packets on + - "5102:5102/tcp" # port to emit XST TCP packets on + - "5706:5706" # unique port for this DS + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ..:/opt/lofar/tango:rw + environment: + - TANGO_HOST=${TANGO_HOST} + working_dir: /opt/lofar/tango + entrypoint: + - bin/start-ds.sh + # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA + # can't know about our Docker port forwarding + - l2ss-xst XST STAT -v -ORBendPoint giop:tcp:0:5706 -ORBendPointPublish giop:tcp:${HOSTNAME}:5706 + restart: unless-stopped diff --git a/docker-compose/elk.yml b/docker-compose/elk.yml index cce66839b499caa0b8948eaeb0c5cc65176be2c9..67f13baee061a74ebd08320f1e9f2f9f3e72f646 100644 --- a/docker-compose/elk.yml +++ b/docker-compose/elk.yml @@ -34,7 +34,9 @@ services: - "5601:5601" # kibana - "9200:9200" # elasticsearch - "5044:5044" # logstash beats input - - "1514:1514" # logstash syslog input + - "1514:1514/tcp" # logstash syslog input + - "1514:1514/udp" # logstash syslog input - "5959:5959" # logstash tcp json input depends_on: - elk-configure-host + restart: unless-stopped diff --git a/docker-compose/elk/logstash/conf.d/20-parse-grafana.conf b/docker-compose/elk/logstash/conf.d/20-parse-grafana.conf new file mode 100644 index 0000000000000000000000000000000000000000..37db44fda67109d7ef8a6beac1193004968a2349 --- /dev/null +++ b/docker-compose/elk/logstash/conf.d/20-parse-grafana.conf @@ -0,0 +1,16 @@ +filter { + if [program] == "grafana" { + kv { } + mutate { + rename => { + "t" => "timestamp" + "lvl" => "level" + "msg" => "message" + } + uppercase => [ "level" ] + } + date { + match => [ "timestamp", "ISO8601" ] + } + } +} diff --git a/docker-compose/elk/logstash/conf.d/21-parse-prometheus.conf b/docker-compose/elk/logstash/conf.d/21-parse-prometheus.conf new file mode 100644 index 0000000000000000000000000000000000000000..b8323625f329af02f9ff33556e408b94ecf7e0b6 --- /dev/null +++ b/docker-compose/elk/logstash/conf.d/21-parse-prometheus.conf @@ -0,0 +1,15 @@ +filter { + if [program] == "prometheus" { + kv { } + mutate { + rename => { + "ts" => "timestamp" + "msg" => "message" + } + uppercase => [ "level" ] + } + date { + match => [ "timestamp", "ISO8601" ] + } + } +} diff --git a/docker-compose/elk/logstash/conf.d/22-parse-tango-rest.conf b/docker-compose/elk/logstash/conf.d/22-parse-tango-rest.conf new file mode 100644 index 0000000000000000000000000000000000000000..5df0cd92bd32625a1eb91220bf4e7a9827799523 --- /dev/null +++ b/docker-compose/elk/logstash/conf.d/22-parse-tango-rest.conf @@ -0,0 +1,14 @@ +filter { + if [program] == "tango-rest" { + grok { + match => { + "message" => "%{TIMESTAMP_ISO8601:timestamp} %{WORD:level} %{GREEDYDATA:message}" + } + "overwrite" => [ "timestamp", "level", "message" ] + } + date { + match => [ "timestamp", "YYYY-MM-dd HH:mm:ss,SSS" ] + timezone => "UTC" + } + } +} diff --git a/docker-compose/elk/logstash/conf.d/23-parse-maria-db.conf b/docker-compose/elk/logstash/conf.d/23-parse-maria-db.conf new file mode 100644 index 0000000000000000000000000000000000000000..0a23fddd078e5e967bc5f791e020faaa20ed632a --- /dev/null +++ b/docker-compose/elk/logstash/conf.d/23-parse-maria-db.conf @@ -0,0 +1,32 @@ +filter { + # mark all our mariadb instances + grok { + match => { + "program" => [ "archiver-maria-db", "tangodb" ] + } + add_tag => [ "mariadb" ] + } + + # parse mariadb output + if "mariadb" in [tags] { + grok { + match => { + "message" => [ + "%{TIMESTAMP_ISO8601:timestamp} .%{WORD:level}. %{GREEDYDATA:message}", + "%{TIMESTAMP_ISO8601:timestamp} 0 .%{WORD:level}. %{GREEDYDATA:message}" + ] + } + "overwrite" => [ "timestamp", "level", "message" ] + } + mutate { + gsub => [ + "level", "Note", "Info" + ] + uppercase => [ "level" ] + } + date { + match => [ "timestamp", "YYYY-MM-dd HH:mm:ssZZ", "YYYY-MM-dd HH:mm:ss", "YYYY-MM-dd H:mm:ss" ] + timezone => "UTC" + } + } +} diff --git a/docker-compose/grafana.yml b/docker-compose/grafana.yml new file mode 100644 index 0000000000000000000000000000000000000000..29c93c52c4dc05849aad10fabac12712c12dd4d7 --- /dev/null +++ b/docker-compose/grafana.yml @@ -0,0 +1,32 @@ +# +# Docker compose file that launches Grafana +# +# Defines: +# - grafana: Grafana +# +version: '2' + +#volumes: +# grafana-data: {} +# grafana-configs: {} + +services: + grafana: + image: grafana + build: + context: grafana + container_name: ${CONTAINER_NAME_PREFIX}grafana + networks: + - control + #volumes: + # - grafana-data:/var/lib/grafana + # - grafana-configs:/etc/grafana + ports: + - "3000:3000" + logging: + driver: syslog + options: + syslog-address: udp://${LOG_HOSTNAME}:1514 + syslog-format: rfc3164 + tag: "{{.Name}}" + restart: unless-stopped diff --git a/docker-compose/grafana/Dockerfile b/docker-compose/grafana/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..e51cce5eeaa0310c1ecd698d8d797e3163ce4457 --- /dev/null +++ b/docker-compose/grafana/Dockerfile @@ -0,0 +1,12 @@ +FROM grafana/grafana + +# Install some plugins +RUN grafana-cli plugins install briangann-datatable-panel +RUN grafana-cli plugins install ae3e-plotly-panel + +COPY grafana.ini /etc/grafana/ + +# Add default configuration through provisioning (see https://grafana.com/docs/grafana/latest/administration/provisioning) +COPY datasources /etc/grafana/provisioning/datasources/ +COPY dashboards /var/lib/grafana/dashboards/ +COPY stationcontrol-dashboards.yaml /etc/grafana/provisioning/dashboards/ diff --git a/docker-compose/grafana/dashboards/docker.json b/docker-compose/grafana/dashboards/docker.json new file mode 100644 index 0000000000000000000000000000000000000000..cc6680ee533d24c9a40a5df2e8020845e32575db --- /dev/null +++ b/docker-compose/grafana/dashboards/docker.json @@ -0,0 +1,108 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 2, + "links": [], + "panels": [ + { + "datasource": "Prometheus", + "description": "Which Docker containers are running on the station.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "fillOpacity": 90, + "lineWidth": 0 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 19, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "alignValue": "center", + "legend": { + "displayMode": "hidden", + "placement": "bottom" + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "never", + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/docker/1\",name=~\".*_R\",name!=\"version_R\"}", + "instant": false, + "interval": "", + "legendFormat": "{{name}}", + "refId": "A" + } + ], + "title": "Docker Containers", + "transformations": [], + "type": "state-timeline" + } + ], + "schemaVersion": 30, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Docker", + "uid": "buKx9ZHnk", + "version": 1 +} diff --git a/docker-compose/grafana/dashboards/home.json b/docker-compose/grafana/dashboards/home.json new file mode 100644 index 0000000000000000000000000000000000000000..4ef59179fa14153bf814975ed74d55c2c92c2d10 --- /dev/null +++ b/docker-compose/grafana/dashboards/home.json @@ -0,0 +1,2433 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": null, + "graphTooltip": 0, + "id": 5, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 15, + "panels": [], + "title": "Devices", + "type": "row" + }, + { + "datasource": "Prometheus", + "description": "Progress of station initialisation", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "yellow", + "value": null + }, + { + "color": "red", + "value": 1 + }, + { + "color": "green", + "value": 100 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 1 + }, + "id": 43, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": false, + "text": {} + }, + "pluginVersion": "8.2.1", + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/boot/1\",name=\"initialisation_progress_R\"}", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Station Initialisation", + "type": "gauge" + }, + { + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "0": { + "color": "green", + "index": 1, + "text": "ON" + }, + "1": { + "color": "red", + "index": 3, + "text": "OFF" + }, + "7": { + "color": "yellow", + "index": 2, + "text": "STANDBY" + }, + "8": { + "color": "red", + "index": 0, + "text": "FAULT" + }, + "11": { + "color": "red", + "index": 4, + "text": "ALARM" + } + }, + "type": "value" + } + ], + "noValue": "???", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "string" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 4, + "y": 1 + }, + "id": 4, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": { + "titleSize": 20, + "valueSize": 20 + }, + "textMode": "value_and_name" + }, + "pluginVersion": "8.2.1", + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{label=\"State\",device=~\"stat/.*/1\"}", + "instant": false, + "interval": "", + "legendFormat": "{{device}}", + "refId": "A" + } + ], + "title": "Device States", + "transformations": [ + { + "id": "renameByRegex", + "options": { + "regex": ".*/(.*)/1", + "renamePattern": "$1" + } + } + ], + "type": "stat" + }, + { + "datasource": "ELK logs", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 10, + "x": 10, + "y": 1 + }, + "id": 32, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "alias": "", + "bucketAggs": [ + { + "field": "extra.lofar_id.keyword", + "id": "2", + "settings": { + "min_doc_count": "0", + "order": "desc", + "orderBy": "_term", + "size": "10" + }, + "type": "terms" + }, + { + "field": "@timestamp", + "id": "3", + "settings": { + "interval": "auto", + "min_doc_count": "0", + "trimEdges": "0" + }, + "type": "date_histogram" + } + ], + "metrics": [ + { + "id": "1", + "type": "count" + } + ], + "query": "level:(ERROR or FATAL)", + "refId": "A", + "timeField": "@timestamp" + } + ], + "title": "Errors", + "transformations": [ + { + "id": "renameByRegex", + "options": { + "regex": "\\(.*/.*/1\\)", + "renamePattern": "" + } + } + ], + "type": "timeseries" + }, + { + "datasource": null, + "description": "Links to other dashboards", + "gridPos": { + "h": 9, + "w": 4, + "x": 20, + "y": 1 + }, + "id": 47, + "options": { + "folderId": 0, + "maxItems": 10, + "query": "", + "showHeadings": false, + "showRecentlyViewed": false, + "showSearch": true, + "showStarred": false, + "tags": [] + }, + "pluginVersion": "8.2.1", + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": false, + "rawSql": "SELECT\n data_time AS \"time\",\n att_conf_id\nFROM att_scalar_devboolean_rw\nWHERE\n $__timeFilter(data_time)\nORDER BY data_time", + "refId": "A", + "select": [ + [ + { + "params": [ + "att_conf_id" + ], + "type": "column" + } + ] + ], + "table": "att_scalar_devboolean_rw", + "timeColumn": "data_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Dashboards", + "type": "dashlist" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 100 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 0, + "y": 7 + }, + "id": 44, + "options": { + "showHeader": false + }, + "pluginVersion": "8.2.1", + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/boot/1\",name=\"initialisation_status_R\"}", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Initialisation status", + "transformations": [ + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "merge", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "Value": true, + "device": true, + "device_attribute{device=\"stat/boot/1\", dim_x=\"1\", dim_y=\"0\", instance=\"tango-prometheus-exporter:8000\", job=\"tango\", label=\"initialisation_status_R\", name=\"initialisation_status_R\", str_value=\"Initialisation completed\", type=\"string\", x=\"0\", y=\"0\"}": true, + "dim_x": true, + "dim_y": true, + "instance": true, + "job": true, + "label": true, + "name": true, + "type": true, + "x": true, + "y": true + }, + "indexByName": { + "Time": 0, + "Value": 5, + "device": 1, + "dim_x": 2, + "dim_y": 3, + "instance": 4, + "job": 6, + "label": 7, + "name": 8, + "str_value": 9, + "type": 10, + "x": 11, + "y": 12 + }, + "renameByName": { + "name": "", + "str_value": "status" + } + } + } + ], + "type": "table" + }, + { + "datasource": "ELK logs", + "description": "List of the errors in the selected timespan", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "filterable": true + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "@timestamp" + }, + "properties": [ + { + "id": "custom.width", + "value": 149 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "level" + }, + "properties": [ + { + "id": "custom.width", + "value": 62 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "program" + }, + "properties": [ + { + "id": "custom.width", + "value": 287 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "extra.logger_name" + }, + "properties": [ + { + "id": "custom.width", + "value": 72 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "extra.lofar_id" + }, + "properties": [ + { + "id": "custom.width", + "value": 196 + } + ] + } + ] + }, + "gridPos": { + "h": 5, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 56, + "options": { + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "8.2.1", + "targets": [ + { + "alias": "", + "bucketAggs": [], + "metrics": [ + { + "hide": false, + "id": "1", + "settings": { + "limit": "500" + }, + "type": "logs" + } + ], + "query": "level:(ERROR or CRIT or FATAL)", + "refId": "A", + "timeField": "@timestamp" + } + ], + "title": "Error Log", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "@version": true, + "_id": true, + "_index": true, + "_source": true, + "_type": true, + "extra.func_name": true, + "extra.interpreter": true, + "extra.interpreter_version": true, + "extra.line": true, + "extra.logger_name": true, + "extra.logstash_async_version": true, + "extra.path": true, + "extra.process_name": true, + "extra.software_version": true, + "extra.tango_device": true, + "extra.thread_name": true, + "highlight": true, + "host": true, + "logsource": true, + "pid": true, + "port": true, + "sort": true, + "tags": true, + "type": true + }, + "indexByName": { + "@timestamp": 0, + "@version": 5, + "_id": 6, + "_index": 7, + "_source": 8, + "_type": 9, + "extra.func_name": 10, + "extra.interpreter": 11, + "extra.interpreter_version": 12, + "extra.line": 13, + "extra.lofar_id": 4, + "extra.logger_name": 14, + "extra.logstash_async_version": 15, + "extra.path": 16, + "extra.process_name": 17, + "extra.software_version": 18, + "extra.tango_device": 19, + "extra.thread_name": 20, + "highlight": 21, + "host": 2, + "level": 1, + "logsource": 22, + "message": 23, + "pid": 24, + "port": 25, + "program": 3, + "sort": 26, + "tags": 27, + "type": 28 + }, + "renameByName": {} + } + } + ], + "type": "table" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 15 + }, + "id": 49, + "panels": [], + "title": "APSCT & APSPU", + "type": "row" + }, + { + "datasource": "Prometheus", + "description": "State of APSCT", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 21, + "x": 0, + "y": 16 + }, + "id": 24, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "name" + }, + "pluginVersion": "8.2.1", + "targets": [ + { + "exemplar": true, + "expr": "1-device_attribute{device=\"stat/apsct/1\",name=\"APSCT_PWR_on_R\"}", + "interval": "", + "legendFormat": "Power", + "refId": "A" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/apsct/1\",name=\"APSCTTR_I2C_error_R\"}", + "hide": false, + "interval": "", + "legendFormat": "I2C", + "refId": "B" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/apsct/1\",name=\"APSCT_PLL_200MHz_error_R\"}", + "hide": false, + "interval": "", + "legendFormat": "PLL", + "refId": "C" + }, + { + "exemplar": true, + "expr": "1-device_attribute{device=\"stat/apsct/1\",name=\"APSCT_PLL_200MHz_locked_R\"}", + "hide": false, + "interval": "", + "legendFormat": "PLL Lock", + "refId": "D" + }, + { + "exemplar": true, + "expr": "1-device_attribute{device=\"stat/apsct/1\",name=\"APSCT_INPUT_10MHz_good_R\"}", + "hide": false, + "interval": "", + "legendFormat": "10MHz", + "refId": "E" + }, + { + "exemplar": true, + "expr": "1-device_attribute{device=\"stat/apsct/1\",name=\"APSCT_INPUT_PPS_good_R\"}", + "hide": false, + "interval": "", + "legendFormat": "PPS", + "refId": "F" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/apsct/1\",name=\"APSCT_PPS_ignore_R\"}", + "hide": false, + "interval": "", + "legendFormat": "PPS used", + "refId": "G" + } + ], + "title": "APS Clock State", + "type": "stat" + }, + { + "datasource": "Prometheus", + "description": "State of APSPU", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 21, + "y": 16 + }, + "id": 50, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "name" + }, + "pluginVersion": "8.2.1", + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/apspu/1\",name=\"APSPUTR_I2C_error_R\"}", + "hide": false, + "interval": "", + "legendFormat": "I2C", + "refId": "B" + } + ], + "title": "APS Power Unit State", + "type": "stat" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 19 + }, + "id": 53, + "panels": [], + "title": "UNB2", + "type": "row" + }, + { + "datasource": "Prometheus", + "description": "State of Unboard 2 I2C Bus", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "green", + "value": 1 + }, + { + "color": "red", + "value": 2 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 20 + }, + "id": 54, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "name" + }, + "pluginVersion": "8.2.1", + "targets": [ + { + "exemplar": true, + "expr": "(1 + (device_attribute{device=\"stat/unb2/1\",name=\"UNB2TR_I2C_bus_error_R\"} != bool 0)) * on(x) device_attribute{device=\"stat/recv/1\",name=\"RCU_mask_RW\"}", + "hide": false, + "interval": "", + "legendFormat": "I2C {{x}}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "(1 + device_attribute{device=\"stat/unb2/1\",name=\"UNB2TR_I2C_bus_PS_error_R\"}) * on(x) device_attribute{device=\"stat/recv/1\",name=\"RCU_mask_RW\"}", + "hide": false, + "interval": "", + "legendFormat": "PS {{x}}", + "refId": "B" + }, + { + "exemplar": true, + "expr": "(1 + sum by (x) (device_attribute{device=\"stat/unb2/1\",name=\"UNB2TR_I2C_bus_FPGA_PS_error_R\"})) * on(x) device_attribute{device=\"stat/recv/1\",name=\"RCU_mask_RW\"}", + "hide": false, + "interval": "", + "legendFormat": "FPGA PS {{x}}", + "refId": "C" + }, + { + "exemplar": true, + "expr": "(1 + sum by (x) (device_attribute{device=\"stat/unb2/1\",name=\"UNB2TR_I2C_bus_DDR4_error_R\"})) * on(x) device_attribute{device=\"stat/recv/1\",name=\"RCU_mask_RW\"}", + "hide": false, + "interval": "", + "legendFormat": "DDR {{x}}", + "refId": "D" + }, + { + "exemplar": true, + "expr": "(1 + sum by (x) (device_attribute{device=\"stat/unb2/1\",name=\"UNB2TR_I2C_bus_QSFP_error_R\"})) * on(x) device_attribute{device=\"stat/recv/1\",name=\"RCU_mask_RW\"}", + "hide": false, + "interval": "", + "legendFormat": "QSFP {{x}}", + "refId": "E" + } + ], + "title": "UNB2 I2C State", + "type": "stat" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 23 + }, + "id": 17, + "panels": [], + "title": "RECV", + "type": "row" + }, + { + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "red", + "value": 0 + }, + { + "color": "green", + "value": 3 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 24 + }, + "id": 21, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "name" + }, + "pluginVersion": "8.2.1", + "targets": [ + { + "exemplar": true, + "expr": "sum by (x)(1 + (device_attribute{device=\"stat/recv/1\",name=\"RCU_ADC_locked_R\"})) * on(x) device_attribute{device=\"stat/recv/1\",name=\"RCU_mask_RW\"} - 3", + "instant": false, + "interval": "", + "legendFormat": "{{y}}", + "refId": "A" + } + ], + "title": "RCU Clock Lock", + "type": "stat" + }, + { + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "red", + "value": 1 + }, + { + "color": "green", + "value": 2 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 24 + }, + "id": 25, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "name" + }, + "pluginVersion": "8.2.1", + "targets": [ + { + "exemplar": true, + "expr": "(1 + (device_attribute{device=\"stat/recv/1\",name=\"RECVTR_I2C_error_R\"} == bool 0)) * on(x) device_attribute{device=\"stat/recv/1\",name=\"RCU_mask_RW\"}", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{y}}", + "refId": "A" + } + ], + "title": "RCU I2C State", + "type": "stat" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "red", + "value": 1 + }, + { + "color": "green", + "value": 2 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 24 + }, + "id": 51, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "name" + }, + "pluginVersion": "8.2.1", + "targets": [ + { + "exemplar": true, + "expr": "(1 + device_attribute{device=\"stat/recv/1\",name=\"RCU_PWR_good_R\"}) * on(x) device_attribute{device=\"stat/recv/1\",name=\"RCU_mask_RW\"}", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{y}}", + "refId": "A" + } + ], + "title": "RCU Power good", + "type": "stat" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 32 + }, + "id": 19, + "panels": [], + "title": "SDP", + "type": "row" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "green", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 0, + "y": 33 + }, + "id": 11, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "name" + }, + "pluginVersion": "8.2.1", + "targets": [ + { + "exemplar": true, + "expr": "(50+50*device_attribute{device=\"stat/sdp/1\",name=\"TR_fpga_communication_error_R\"}) * on(x) device_attribute{device=\"stat/sdp/1\",name=\"TR_fpga_mask_R\"}", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{x}}", + "refId": "A" + } + ], + "title": "FPGA communication", + "transformations": [], + "type": "stat" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "green", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 5, + "y": 33 + }, + "id": 9, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "name" + }, + "pluginVersion": "8.2.1", + "targets": [ + { + "exemplar": true, + "expr": "(100-50*device_attribute{device=\"stat/sdp/1\",name=\"FPGA_processing_enable_R\"}) * on(x) device_attribute{device=\"stat/sdp/1\",name=\"TR_fpga_mask_R\"}", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{x}}", + "refId": "A" + } + ], + "title": "FPGA processing enabled", + "transformations": [], + "type": "stat" + }, + { + "datasource": "Prometheus", + "description": "Number of inputs that are fed from the SDP wave-form generator", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "0": { + "index": 0, + "text": "OFF" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 10, + "y": 33 + }, + "id": 12, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "8.2.1", + "targets": [ + { + "exemplar": true, + "expr": "sum(sum by(x) (device_attribute{device=\"stat/sdp/1\",name=\"FPGA_wg_enable_RW\"}) * on(x) device_attribute{device=\"stat/sdp/1\",name=\"TR_fpga_mask_R\"})", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{x}}", + "refId": "A" + } + ], + "title": "Waveform generator", + "transformations": [], + "type": "stat" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 41 + }, + "id": 27, + "panels": [], + "title": "SST", + "type": "row" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "green", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 0, + "y": 42 + }, + "id": 28, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "name" + }, + "pluginVersion": "8.2.1", + "targets": [ + { + "exemplar": true, + "expr": "(100-50*device_attribute{device=\"stat/sst/1\",name=\"FPGA_sst_offload_enable_R\"}) * on(x) device_attribute{device=\"stat/sdp/1\",name=\"TR_fpga_mask_R\"}", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{x}}", + "refId": "A" + } + ], + "title": "SST offloading enabled", + "transformations": [], + "type": "stat" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "green", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + }, + "unit": "pps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 5, + "y": 42 + }, + "id": 29, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "exemplar": true, + "expr": "rate(device_attribute{device=\"stat/sst/1\",name=\"nof_invalid_packets_R\"}[1m])", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "invalid", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(device_attribute{device=\"stat/sst/1\",name=\"nof_packets_dropped_R\"}[1m])", + "hide": false, + "interval": "", + "legendFormat": "dropped", + "refId": "B" + }, + { + "exemplar": true, + "expr": "rate(device_attribute{device=\"stat/sst/1\",name=\"nof_payload_errors_R\"}[1m])", + "hide": false, + "interval": "", + "legendFormat": "payload errors {{x}}", + "refId": "C" + } + ], + "title": "SST packet errors", + "transformations": [], + "type": "timeseries" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "green", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 10, + "y": 42 + }, + "id": 30, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(device_attribute{device=\"stat/sst/1\",name=\"nof_bytes_received_R\"}[1m]))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{x}}", + "refId": "A" + } + ], + "title": "SST bytes received", + "transformations": [], + "type": "timeseries" + }, + { + "datasource": "Prometheus", + "description": "Rate of SSTs replicated to connected clients.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "green", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 15, + "y": 42 + }, + "id": 33, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "exemplar": true, + "expr": "rate(device_attribute{device=\"stat/sst/1\",name=\"replicator_nof_bytes_sent_R\"}[1m])", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{x}}", + "refId": "A" + } + ], + "title": "SST bytes sent", + "transformations": [], + "type": "timeseries" + }, + { + "datasource": "Prometheus", + "description": "Load of TCPReplicator class, which sends statistics packets to connected clients.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMax": 5, + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "green", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 20, + "y": 42 + }, + "id": 34, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/sst/1\",name=\"replicator_nof_tasks_pending_R\"}", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{x}}", + "refId": "A" + } + ], + "title": "SST Replicator load", + "transformations": [], + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 50 + }, + "id": 36, + "panels": [], + "title": "XST", + "type": "row" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "green", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 0, + "y": 51 + }, + "id": 37, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "name" + }, + "pluginVersion": "8.2.1", + "targets": [ + { + "exemplar": true, + "expr": "(100-50*device_attribute{device=\"stat/xst/1\",name=\"FPGA_xst_offload_enable_R\"}) * on(x) device_attribute{device=\"stat/sdp/1\",name=\"TR_fpga_mask_R\"}", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{x}}", + "refId": "A" + } + ], + "title": "XST offloading enabled", + "transformations": [], + "type": "stat" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "green", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + }, + "unit": "pps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 5, + "y": 51 + }, + "id": 38, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "exemplar": true, + "expr": "rate(device_attribute{device=\"stat/xst/1\",name=\"nof_invalid_packets_R\"}[1m])", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "invalid", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(device_attribute{device=\"stat/xst/1\",name=\"nof_packets_dropped_R\"}[1m])", + "hide": false, + "interval": "", + "legendFormat": "dropped", + "refId": "B" + }, + { + "exemplar": true, + "expr": "rate(device_attribute{device=\"stat/xst/1\",name=\"nof_payload_errors_R\"}[1m])", + "hide": false, + "interval": "", + "legendFormat": "payload errors {{x}}", + "refId": "C" + } + ], + "title": "XST packet errors", + "transformations": [], + "type": "timeseries" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "green", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 10, + "y": 51 + }, + "id": 39, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(device_attribute{device=\"stat/xst/1\",name=\"nof_bytes_received_R\"}[1m]))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{x}}", + "refId": "A" + } + ], + "title": "XST bytes received", + "transformations": [], + "type": "timeseries" + }, + { + "datasource": "Prometheus", + "description": "Rate of XSTs replicated to connected clients.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "green", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 15, + "y": 51 + }, + "id": 40, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "exemplar": true, + "expr": "rate(device_attribute{device=\"stat/xst/1\",name=\"replicator_nof_bytes_sent_R\"}[1m])", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{x}}", + "refId": "A" + } + ], + "title": "XST bytes sent", + "transformations": [], + "type": "timeseries" + }, + { + "datasource": "Prometheus", + "description": "Load of TCPReplicator class, which sends statistics packets to connected clients.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMax": 5, + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "green", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 20, + "y": 51 + }, + "id": 41, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/xst/1\",name=\"replicator_nof_tasks_pending_R\"}", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{x}}", + "refId": "A" + } + ], + "title": "XST Replicator load", + "transformations": [], + "type": "timeseries" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "green", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 0, + "y": 55 + }, + "id": 45, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "name" + }, + "pluginVersion": "8.2.1", + "targets": [ + { + "exemplar": true, + "expr": "(100-50*device_attribute{device=\"stat/xst/1\",name=\"FPGA_xst_processing_enable_R\"}) * on(x) device_attribute{device=\"stat/sdp/1\",name=\"TR_fpga_mask_R\"}", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{x}}", + "refId": "A" + } + ], + "title": "XST processing enabled", + "transformations": [], + "type": "stat" + } + ], + "refresh": false, + "schemaVersion": 31, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Home", + "uid": "nC8N_kO7k", + "version": 6 +} diff --git a/docker-compose/grafana/dashboards/sensors.json b/docker-compose/grafana/dashboards/sensors.json new file mode 100644 index 0000000000000000000000000000000000000000..cbb6e6d0dab36965e0d744fd35ea1392de0c33d3 --- /dev/null +++ b/docker-compose/grafana/dashboards/sensors.json @@ -0,0 +1,1190 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": null, + "graphTooltip": 0, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 16, + "title": "Temperatures", + "type": "row" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "celsius" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 0, + "y": 1 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/sdp/1\",name=\"FPGA_temp_R\"} != 0", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{x}}", + "refId": "A" + } + ], + "title": "FPGA Temperatures", + "transformations": [], + "type": "timeseries" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic", + "seriesBy": "max" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 85 + } + ] + }, + "unit": "celsius" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 5, + "y": 1 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_FPGA_POL_CORE_TEMP_R\"} ", + "interval": "", + "legendFormat": "Core board {{x}} node {{y}}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_FPGA_POL_ERAM_TEMP_R\"} ", + "hide": false, + "interval": "", + "legendFormat": "ERAM board {{x}} node {{y}}", + "refId": "B" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_FPGA_POL_RXGXB_TEMP_R\"} ", + "hide": false, + "interval": "", + "legendFormat": "TrRx board {{x}} node {{y}}", + "refId": "C" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_FPGA_POL_TXGB_TEMP_R\"} ", + "hide": false, + "interval": "", + "legendFormat": "TrHx board {{x}} node {{y}}", + "refId": "D" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_FPGA_POL_PGM_TEMP_R\"} ", + "hide": false, + "interval": "", + "legendFormat": "IO board {{x}} node {{y}}", + "refId": "E" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_FPGA_POL_HGXB_TEMP_R\"} ", + "hide": false, + "interval": "", + "legendFormat": "HGXB board {{x}} node {{y}}", + "refId": "F" + } + ], + "title": "Uniboard2 FPGA POL Temperatures", + "transformations": [], + "type": "timeseries" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic", + "seriesBy": "max" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 85 + } + ] + }, + "unit": "celsius" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 10, + "y": 1 + }, + "id": 22, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_FPGA_QSFP_CAGE_TEMP_R\"}", + "interval": "", + "legendFormat": "FPGA QSFP Cage {{x}}, {{y}} ", + "refId": "A" + } + ], + "title": "Uniboard2 QSFP Cage Temperatures", + "type": "timeseries" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic", + "seriesBy": "max" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 85 + } + ] + }, + "unit": "celsius" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 15, + "y": 1 + }, + "id": 23, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_FPGA_DDR4_SLOT_TEMP_R\"}", + "interval": "", + "legendFormat": "FPGA QSFP Cage {{x}}, {{y}} ", + "refId": "A" + } + ], + "title": "Uniboard2 DDR4 Temperatures", + "type": "timeseries" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "celsius" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 0, + "y": 9 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/recv/1\",name=\"RCU_TEMP_R\"}", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{x}}", + "refId": "A" + } + ], + "title": "RCU Temperatures", + "transformations": [], + "type": "timeseries" + }, + { + "datasource": "Prometheus", + "description": "Temperatures reported by APSCT and APSPU", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic", + "seriesBy": "max" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 85 + } + ] + }, + "unit": "celsius" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 5, + "y": 9 + }, + "id": 24, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/apsct/1\",name=~\"APSCT_TEMP_R\"}", + "interval": "", + "legendFormat": "{{name}}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/apspu/1\",name=~\"APSPU_.*_TEMP_R\"}", + "hide": false, + "interval": "", + "legendFormat": "{{name}}", + "refId": "B" + } + ], + "title": "APS Temperatures", + "type": "timeseries" + }, + { + "datasource": "Prometheus", + "description": "Temperature sensors of the power supply on each board", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic", + "seriesBy": "max" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 85 + } + ] + }, + "unit": "celsius" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 15, + "y": 9 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_POL_QSFP_N01_TEMP_R\"} ", + "interval": "", + "legendFormat": "QSFP N01 board {{x}}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_POL_QSFP_N23_TEMP_R\"} ", + "hide": false, + "interval": "", + "legendFormat": "QSFP N23 board {{x}}", + "refId": "B" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_POL_SWITCH_1V2_TEMP_R\"} ", + "hide": false, + "interval": "", + "legendFormat": "Switch 1v2 board {{x}}", + "refId": "C" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_POL_SWITCH_PHY_TEMP_R\"} ", + "hide": false, + "interval": "", + "legendFormat": "Switch PHY board {{x}}", + "refId": "D" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_POL_CLOCK_TEMP_R\"} ", + "hide": false, + "interval": "", + "legendFormat": "Clock PWR board {{x}}", + "refId": "E" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_DC_DC_48V_12V_TEMP_R\"} ", + "hide": false, + "interval": "", + "legendFormat": "DC-DC board {{x}}", + "refId": "F" + } + ], + "title": "Uniboard2 Power Supply Temperatures", + "type": "timeseries" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 17 + }, + "id": 18, + "panels": [], + "title": "Voltages", + "type": "row" + }, + { + "datasource": "Prometheus", + "description": "Voltage sensors of the power supplies of the APS", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic", + "seriesBy": "max" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMax": 10, + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 85 + } + ] + }, + "unit": "volt" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 0, + "y": 18 + }, + "id": 21, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/apspu/1\",name=~\"APSPU_.*_VOUT_R\"}", + "interval": "", + "legendFormat": "{{name}} {{x}} ", + "refId": "A" + } + ], + "title": "APSPU Voltages", + "type": "timeseries" + }, + { + "datasource": "Prometheus", + "description": "Voltage sensors of each node on each board", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMax": 2, + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 85 + } + ] + }, + "unit": "volt" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 5, + "y": 18 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_FPGA_POL_CORE_VOUT_R\"}", + "interval": "", + "legendFormat": "Core board {{x}} node {{y}}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_FPGA_POL_ERAM_VOUT_R\"}", + "hide": false, + "interval": "", + "legendFormat": "ERAM board {{x}} node {{y}}", + "refId": "B" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_FPGA_POL_RXGXB_VOUT_R\"}", + "hide": false, + "interval": "", + "legendFormat": "TrRx board {{x}} node {{y}}", + "refId": "C" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_FPGA_POL_TXGB_VOUT_R\"}", + "hide": false, + "interval": "", + "legendFormat": "TrHx board {{x}} node {{y}}", + "refId": "D" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_FPGA_POL_PGM_VOUT_R\"}", + "hide": false, + "interval": "", + "legendFormat": "IO board {{x}} node {{y}}", + "refId": "E" + } + ], + "title": "Uniboard2 Voltages", + "type": "timeseries" + }, + { + "datasource": "Prometheus", + "description": "Voltage sensors of the power supply on each board", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic", + "seriesBy": "max" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 85 + } + ] + }, + "unit": "volt" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 10, + "y": 18 + }, + "id": 12, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_POL_QSFP_N01_VOUT_R\"}", + "interval": "", + "legendFormat": "QSFP N01 board {{x}} ", + "refId": "A" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_POL_QSFP_N23_VOUT_R\"}", + "hide": false, + "interval": "", + "legendFormat": "QSFP N23 board {{x}}", + "refId": "B" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_POL_SWITCH_1V2_VOUT_R\"}", + "hide": false, + "interval": "", + "legendFormat": "Switch 1v2 board {{x}}", + "refId": "C" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_POL_SWITCH_PHY_VOUT_R\"}", + "hide": false, + "interval": "", + "legendFormat": "Switch PHY board {{x}}", + "refId": "D" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_POL_CLOCK_VOUT_R\"}", + "hide": false, + "interval": "", + "legendFormat": "Clock PWR board {{x}}", + "refId": "E" + }, + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/unb2/1\",name=\"UNB2_DC_DC_48V_12V_VOUT_R\"}", + "hide": false, + "interval": "", + "legendFormat": "DC-DC board {{x}}", + "refId": "F" + } + ], + "title": "Uniboard2 Power Supply Voltages", + "type": "timeseries" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 26 + }, + "id": 20, + "panels": [], + "title": "Clock stability", + "type": "row" + }, + { + "datasource": "Prometheus", + "description": "Measured difference between PTP and PPS", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 60000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 0.001 + }, + { + "color": "red", + "value": 0.1 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 0, + "y": 27 + }, + "id": 14, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/sdp/1\",name=\"TR_tod_pps_delta_R\"}", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{x}}", + "refId": "A" + } + ], + "title": "FPGA Clock offset", + "transformations": [], + "type": "timeseries" + } + ], + "schemaVersion": 31, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Sensors", + "uid": "KMRmQzd7z", + "version": 1 +} diff --git a/docker-compose/grafana/dashboards/version-information.json b/docker-compose/grafana/dashboards/version-information.json new file mode 100644 index 0000000000000000000000000000000000000000..abd1f076841d0e4fd86dbd568226356f8270b697 --- /dev/null +++ b/docker-compose/grafana/dashboards/version-information.json @@ -0,0 +1,685 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 2, + "links": [], + "panels": [ + { + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 7, + "title": "SC", + "type": "row" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "version" + }, + "properties": [ + { + "id": "custom.width", + "value": 1533 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "device" + }, + "properties": [ + { + "id": "custom.width", + "value": 136 + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 1 + }, + "id": 9, + "options": { + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{name=\"version_R\"}", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Device software versions", + "transformations": [ + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "Value": true, + "device": false, + "dim_x": true, + "dim_y": true, + "instance": true, + "job": true, + "label": true, + "name": true, + "type": true, + "x": true, + "y": true + }, + "indexByName": {}, + "renameByName": { + "Time": "time", + "Value": "count", + "str_value": "version" + } + } + } + ], + "type": "table" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 7 + }, + "id": 5, + "panels": [], + "title": "SDP", + "type": "row" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "version" + }, + "properties": [ + { + "id": "custom.width", + "value": 1907 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "x" + }, + "properties": [ + { + "id": "custom.width", + "value": 114 + } + ] + } + ] + }, + "gridPos": { + "h": 17, + "w": 8, + "x": 0, + "y": 8 + }, + "id": 2, + "options": { + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/sdp/1\",name=\"FPGA_firmware_version_R\"}", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Firmware versions", + "transformations": [ + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "Value": true, + "device": true, + "dim_x": true, + "dim_y": true, + "instance": true, + "job": true, + "label": true, + "name": true, + "str_value": false, + "type": true, + "y": true + }, + "indexByName": { + "Time": 1, + "Value": 12, + "device": 2, + "dim_x": 3, + "dim_y": 4, + "instance": 5, + "job": 6, + "label": 7, + "name": 8, + "str_value": 9, + "type": 10, + "x": 0, + "y": 11 + }, + "renameByName": { + "Time": "time", + "Value": "count", + "str_value": "version" + } + } + }, + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "x" + } + ] + } + } + ], + "type": "table" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "version" + }, + "properties": [ + { + "id": "custom.width", + "value": 1907 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "x" + }, + "properties": [ + { + "id": "custom.width", + "value": 114 + } + ] + } + ] + }, + "gridPos": { + "h": 17, + "w": 8, + "x": 8, + "y": 8 + }, + "id": 13, + "options": { + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/sdp/1\",name=\"FPGA_hardware_version_R\"}", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Hardware versions", + "transformations": [ + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "Value": true, + "device": true, + "dim_x": true, + "dim_y": true, + "instance": true, + "job": true, + "label": true, + "name": true, + "str_value": false, + "type": true, + "y": true + }, + "indexByName": { + "Time": 1, + "Value": 12, + "device": 2, + "dim_x": 3, + "dim_y": 4, + "instance": 5, + "job": 6, + "label": 7, + "name": 8, + "str_value": 9, + "type": 10, + "x": 0, + "y": 11 + }, + "renameByName": { + "Time": "time", + "Value": "count", + "str_value": "version" + } + } + }, + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "x" + } + ] + } + } + ], + "type": "table" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "version" + }, + "properties": [ + { + "id": "custom.width", + "value": 497 + } + ] + } + ] + }, + "gridPos": { + "h": 3, + "w": 5, + "x": 16, + "y": 8 + }, + "id": 8, + "options": { + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "exemplar": true, + "expr": "count(device_attribute{device=\"stat/sdp/1\",name=\"TR_software_version_R\"}) by (str_value)", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Translator software versions", + "transformations": [ + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "Value": true + }, + "indexByName": {}, + "renameByName": { + "Time": "time", + "Value": "count", + "str_value": "version" + } + } + } + ], + "type": "table" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 25 + }, + "id": 11, + "panels": [], + "title": "RECV", + "type": "row" + }, + { + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "version" + }, + "properties": [ + { + "id": "custom.width", + "value": 497 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "x" + }, + "properties": [ + { + "id": "custom.width", + "value": 81 + } + ] + } + ] + }, + "gridPos": { + "h": 32, + "w": 7, + "x": 0, + "y": 26 + }, + "id": 12, + "options": { + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "8.1.2", + "targets": [ + { + "exemplar": true, + "expr": "device_attribute{device=\"stat/recv/1\",name=\"RCU_version_R\"}", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "RCU versions", + "transformations": [ + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "Value": true, + "device": true, + "dim_x": true, + "dim_y": true, + "instance": true, + "job": true, + "label": true, + "name": true, + "type": true, + "y": true + }, + "indexByName": { + "Time": 1, + "Value": 12, + "device": 2, + "dim_x": 3, + "dim_y": 4, + "instance": 5, + "job": 6, + "label": 7, + "name": 8, + "str_value": 9, + "type": 10, + "x": 0, + "y": 11 + }, + "renameByName": { + "Time": "time", + "Value": "count", + "str_value": "version" + } + } + }, + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "x" + } + ] + } + } + ], + "type": "table" + } + ], + "schemaVersion": 30, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Versions", + "uid": "eR9posS7z", + "version": 1 +} diff --git a/docker-compose/grafana/datasources/archiver-maria-db.yaml b/docker-compose/grafana/datasources/archiver-maria-db.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c809d294269683f12ca82a9f28d6019c85f96723 --- /dev/null +++ b/docker-compose/grafana/datasources/archiver-maria-db.yaml @@ -0,0 +1,40 @@ +apiVersion: 1 + +datasources: + # <string, required> name of the datasource. Required + - name: Archiver + # <string, required> datasource type. Required + type: mysql + # <string, required> access mode. proxy or direct (Server or Browser in the UI). Required + access: proxy + # <int> org id. will default to orgId 1 if not specified + orgId: 1 + # <string> custom UID which can be used to reference this datasource in other parts of the configuration, if not specified will be generated automatically + uid: ZqAMHGN7z + # <string> url + url: archiver-maria-db + # <string> Deprecated, use secureJsonData.password + password: + # <string> database user, if used + user: tango + # <string> database name, if used + database: hdbpp + # <bool> enable/disable basic auth + basicAuth: false + # <string> basic auth username + basicAuthUser: + # <string> Deprecated, use secureJsonData.basicAuthPassword + basicAuthPassword: + # <bool> enable/disable with credentials headers + withCredentials: + # <bool> mark as default datasource. Max one per org + isDefault: true + # <map> fields that will be converted to json and stored in jsonData + jsonData: + # <string> json object of data that will be encrypted. + secureJsonData: + # <string> database password, if used + password: tango + version: 1 + # <bool> allow users to edit datasources from the UI. + editable: false diff --git a/docker-compose/grafana/datasources/elk.yaml b/docker-compose/grafana/datasources/elk.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7dc0535bf5bfcfd9446836d8425dd74a320918e6 --- /dev/null +++ b/docker-compose/grafana/datasources/elk.yaml @@ -0,0 +1,44 @@ +apiVersion: 1 + +datasources: + # <string, required> name of the datasource. Required + - name: ELK logs + # <string, required> datasource type. Required + type: elasticsearch + # <string, required> access mode. proxy or direct (Server or Browser in the UI). Required + access: proxy + # <int> org id. will default to orgId 1 if not specified + orgId: 1 + # <string> custom UID which can be used to reference this datasource in other parts of the configuration, if not specified will be generated automatically + uid: RuQjz8V7z + # <string> url + url: elk:9200 + # <string> Deprecated, use secureJsonData.password + password: + # <string> database user, if used + user: + # <string> database name, if used + database: logstash-* + # <bool> enable/disable basic auth + basicAuth: false + # <string> basic auth username + basicAuthUser: + # <string> Deprecated, use secureJsonData.basicAuthPassword + basicAuthPassword: + # <bool> enable/disable with credentials headers + withCredentials: + # <bool> mark as default datasource. Max one per org + isDefault: false + # <map> fields that will be converted to json and stored in jsonData + jsonData: + esVersion: 7.10.0 + includeFrozen: false + logLevelField: + logMessageField: + maxConcurrentShardRequests: 5 + timeField: "@timestamp" + # <string> json object of data that will be encrypted. + secureJsonData: + version: 1 + # <bool> allow users to edit datasources from the UI. + editable: false diff --git a/docker-compose/grafana/datasources/prometheus.yaml b/docker-compose/grafana/datasources/prometheus.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e271f4a9c609a4e11b36bb688bed6f01faae0d74 --- /dev/null +++ b/docker-compose/grafana/datasources/prometheus.yaml @@ -0,0 +1,39 @@ +apiVersion: 1 + +datasources: + # <string, required> name of the datasource. Required + - name: Prometheus + # <string, required> datasource type. Required + type: prometheus + # <string, required> access mode. proxy or direct (Server or Browser in the UI). Required + access: proxy + # <int> org id. will default to orgId 1 if not specified + orgId: 1 + # <string> custom UID which can be used to reference this datasource in other parts of the configuration, if not specified will be generated automatically + uid: 6W2nM-Vnz + # <string> url + url: prometheus:9090 + # <string> Deprecated, use secureJsonData.password + password: + # <string> database user, if used + user: + # <string> database name, if used + database: + # <bool> enable/disable basic auth + basicAuth: false + # <string> basic auth username + basicAuthUser: + # <string> Deprecated, use secureJsonData.basicAuthPassword + basicAuthPassword: + # <bool> enable/disable with credentials headers + withCredentials: + # <bool> mark as default datasource. Max one per org + isDefault: false + # <map> fields that will be converted to json and stored in jsonData + jsonData: + httpMethod: POST + # <string> json object of data that will be encrypted. + secureJsonData: + version: 1 + # <bool> allow users to edit datasources from the UI. + editable: false diff --git a/docker-compose/grafana/datasources/tangodb.yaml b/docker-compose/grafana/datasources/tangodb.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9a962a2417f0c963249b53fde925d8c11fcdc996 --- /dev/null +++ b/docker-compose/grafana/datasources/tangodb.yaml @@ -0,0 +1,40 @@ +apiVersion: 1 + +datasources: + # <string, required> name of the datasource. Required + - name: TangoDB + # <string, required> datasource type. Required + type: mysql + # <string, required> access mode. proxy or direct (Server or Browser in the UI). Required + access: proxy + # <int> org id. will default to orgId 1 if not specified + orgId: 1 + # <string> custom UID which can be used to reference this datasource in other parts of the configuration, if not specified will be generated automatically + uid: d5_heb47k + # <string> url + url: tangodb + # <string> Deprecated, use secureJsonData.password + password: + # <string> database user, if used + user: tango + # <string> database name, if used + database: hdbpp + # <bool> enable/disable basic auth + basicAuth: false + # <string> basic auth username + basicAuthUser: + # <string> Deprecated, use secureJsonData.basicAuthPassword + basicAuthPassword: + # <bool> enable/disable with credentials headers + withCredentials: + # <bool> mark as default datasource. Max one per org + isDefault: false + # <map> fields that will be converted to json and stored in jsonData + jsonData: + # <string> json object of data that will be encrypted. + secureJsonData: + # <string> database password, if used + password: tango + version: 1 + # <bool> allow users to edit datasources from the UI. + editable: false diff --git a/docker-compose/grafana/grafana.ini b/docker-compose/grafana/grafana.ini new file mode 100644 index 0000000000000000000000000000000000000000..82f1f4bb004e5ba3c1078226e96decf09cdca4f5 --- /dev/null +++ b/docker-compose/grafana/grafana.ini @@ -0,0 +1,1006 @@ +##################### Grafana Configuration Example ##################### +# +# Everything has defaults so you only need to uncomment things you want to +# change + +# possible values : production, development +;app_mode = production + +# instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty +;instance_name = ${HOSTNAME} + +#################################### Paths #################################### +[paths] +# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used) +;data = /var/lib/grafana + +# Temporary files in `data` directory older than given duration will be removed +;temp_data_lifetime = 24h + +# Directory where grafana can store logs +;logs = /var/log/grafana + +# Directory where grafana will automatically scan and look for plugins +;plugins = /var/lib/grafana/plugins + +# folder that contains provisioning config files that grafana will apply on startup and while running. +;provisioning = conf/provisioning + +#################################### Server #################################### +[server] +# Protocol (http, https, h2, socket) +;protocol = http + +# The ip address to bind to, empty will bind to all interfaces +;http_addr = + +# The http port to use +;http_port = 3000 + +# The public facing domain name used to access grafana from a browser +;domain = localhost + +# Redirect to correct domain if host header does not match domain +# Prevents DNS rebinding attacks +;enforce_domain = false + +# The full public facing url you use in browser, used for redirects and emails +# If you use reverse proxy and sub path specify full url (with sub path) +;root_url = %(protocol)s://%(domain)s:%(http_port)s/ + +# Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons. +;serve_from_sub_path = false + +# Log web requests +;router_logging = false + +# the path relative working path +;static_root_path = public + +# enable gzip +;enable_gzip = false + +# https certs & key file +;cert_file = +;cert_key = + +# Unix socket path +;socket = + +# CDN Url +;cdn_url = + +# Sets the maximum time using a duration format (5s/5m/5ms) before timing out read of an incoming request and closing idle connections. +# `0` means there is no timeout for reading the request. +;read_timeout = 0 + +#################################### Database #################################### +[database] +# You can configure the database connection by specifying type, host, name, user and password +# as separate properties or as on string using the url properties. + +# Either "mysql", "postgres" or "sqlite3", it's your choice +;type = sqlite3 +;host = 127.0.0.1:3306 +;name = grafana +;user = root +# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;""" +;password = + +# Use either URL or the previous fields to configure the database +# Example: mysql://user:secret@host:port/database +;url = + +# For "postgres" only, either "disable", "require" or "verify-full" +;ssl_mode = disable + +# Database drivers may support different transaction isolation levels. +# Currently, only "mysql" driver supports isolation levels. +# If the value is empty - driver's default isolation level is applied. +# For "mysql" use "READ-UNCOMMITTED", "READ-COMMITTED", "REPEATABLE-READ" or "SERIALIZABLE". +;isolation_level = + +;ca_cert_path = +;client_key_path = +;client_cert_path = +;server_cert_name = + +# For "sqlite3" only, path relative to data_path setting +;path = grafana.db + +# Max idle conn setting default is 2 +;max_idle_conn = 2 + +# Max conn setting default is 0 (mean not set) +;max_open_conn = + +# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours) +;conn_max_lifetime = 14400 + +# Set to true to log the sql calls and execution times. +;log_queries = + +# For "sqlite3" only. cache mode setting used for connecting to the database. (private, shared) +;cache_mode = private + +################################### Data sources ######################### +[datasources] +# Upper limit of data sources that Grafana will return. This limit is a temporary configuration and it will be deprecated when pagination will be introduced on the list data sources API. +;datasource_limit = 5000 + +#################################### Cache server ############################# +[remote_cache] +# Either "redis", "memcached" or "database" default is "database" +;type = database + +# cache connectionstring options +# database: will use Grafana primary database. +# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'. +# memcache: 127.0.0.1:11211 +;connstr = + +#################################### Data proxy ########################### +[dataproxy] + +# This enables data proxy logging, default is false +;logging = false + +# How long the data proxy waits to read the headers of the response before timing out, default is 30 seconds. +# This setting also applies to core backend HTTP data sources where query requests use an HTTP client with timeout set. +;timeout = 30 + +# How long the data proxy waits to establish a TCP connection before timing out, default is 10 seconds. +;dialTimeout = 10 + +# How many seconds the data proxy waits before sending a keepalive probe request. +;keep_alive_seconds = 30 + +# How many seconds the data proxy waits for a successful TLS Handshake before timing out. +;tls_handshake_timeout_seconds = 10 + +# How many seconds the data proxy will wait for a server's first response headers after +# fully writing the request headers if the request has an "Expect: 100-continue" +# header. A value of 0 will result in the body being sent immediately, without +# waiting for the server to approve. +;expect_continue_timeout_seconds = 1 + +# Optionally limits the total number of connections per host, including connections in the dialing, +# active, and idle states. On limit violation, dials will block. +# A value of zero (0) means no limit. +;max_conns_per_host = 0 + +# The maximum number of idle connections that Grafana will keep alive. +;max_idle_connections = 100 + +# How many seconds the data proxy keeps an idle connection open before timing out. +;idle_conn_timeout_seconds = 90 + +# If enabled and user is not anonymous, data proxy will add X-Grafana-User header with username into the request, default is false. +;send_user_header = false + +#################################### Analytics #################################### +[analytics] +# Server reporting, sends usage counters to stats.grafana.org every 24 hours. +# No ip addresses are being tracked, only simple counters to track +# running instances, dashboard and error counts. It is very helpful to us. +# Change this option to false to disable reporting. +;reporting_enabled = true + +# The name of the distributor of the Grafana instance. Ex hosted-grafana, grafana-labs +;reporting_distributor = grafana-labs + +# Set to false to disable all checks to https://grafana.net +# for new versions (grafana itself and plugins), check is used +# in some UI views to notify that grafana or plugin update exists +# This option does not cause any auto updates, nor send any information +# only a GET request to http://grafana.com to get latest versions +;check_for_updates = true + +# Google Analytics universal tracking code, only enabled if you specify an id here +;google_analytics_ua_id = + +# Google Tag Manager ID, only enabled if you specify an id here +;google_tag_manager_id = + +#################################### Security #################################### +[security] +# disable creation of admin user on first start of grafana +;disable_initial_admin_creation = false + +# default admin user, created on startup +;admin_user = admin + +# default admin password, can be changed before first start of grafana, or in profile settings +;admin_password = admin + +# used for signing +;secret_key = SW2YcwTIb9zpOOhoPsMm + +# disable gravatar profile images +;disable_gravatar = false + +# data source proxy whitelist (ip_or_domain:port separated by spaces) +;data_source_proxy_whitelist = + +# disable protection against brute force login attempts +;disable_brute_force_login_protection = false + +# set to true if you host Grafana behind HTTPS. default is false. +;cookie_secure = false + +# set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict", "none" and "disabled" +;cookie_samesite = lax + +# set to true if you want to allow browsers to render Grafana in a <frame>, <iframe>, <embed> or <object>. default is false. +;allow_embedding = false + +# Set to true if you want to enable http strict transport security (HSTS) response header. +# This is only sent when HTTPS is enabled in this configuration. +# HSTS tells browsers that the site should only be accessed using HTTPS. +;strict_transport_security = false + +# Sets how long a browser should cache HSTS. Only applied if strict_transport_security is enabled. +;strict_transport_security_max_age_seconds = 86400 + +# Set to true if to enable HSTS preloading option. Only applied if strict_transport_security is enabled. +;strict_transport_security_preload = false + +# Set to true if to enable the HSTS includeSubDomains option. Only applied if strict_transport_security is enabled. +;strict_transport_security_subdomains = false + +# Set to true to enable the X-Content-Type-Options response header. +# The X-Content-Type-Options response HTTP header is a marker used by the server to indicate that the MIME types advertised +# in the Content-Type headers should not be changed and be followed. +;x_content_type_options = true + +# Set to true to enable the X-XSS-Protection header, which tells browsers to stop pages from loading +# when they detect reflected cross-site scripting (XSS) attacks. +;x_xss_protection = true + +# Enable adding the Content-Security-Policy header to your requests. +# CSP allows to control resources the user agent is allowed to load and helps prevent XSS attacks. +;content_security_policy = false + +# Set Content Security Policy template used when adding the Content-Security-Policy header to your requests. +# $NONCE in the template includes a random nonce. +# $ROOT_PATH is server.root_url without the protocol. +;content_security_policy_template = """script-src 'self' 'unsafe-eval' 'unsafe-inline' 'strict-dynamic' $NONCE;object-src 'none';font-src 'self';style-src 'self' 'unsafe-inline' blob:;img-src * data:;base-uri 'self';connect-src 'self' grafana.com ws://$ROOT_PATH wss://$ROOT_PATH;manifest-src 'self';media-src 'none';form-action 'self';""" + +#################################### Snapshots ########################### +[snapshots] +# snapshot sharing options +;external_enabled = true +;external_snapshot_url = https://snapshots-origin.raintank.io +;external_snapshot_name = Publish to snapshot.raintank.io + +# Set to true to enable this Grafana instance act as an external snapshot server and allow unauthenticated requests for +# creating and deleting snapshots. +;public_mode = false + +# remove expired snapshot +;snapshot_remove_expired = true + +#################################### Dashboards History ################## +[dashboards] +# Number dashboard versions to keep (per dashboard). Default: 20, Minimum: 1 +;versions_to_keep = 20 + +# Minimum dashboard refresh interval. When set, this will restrict users to set the refresh interval of a dashboard lower than given interval. Per default this is 5 seconds. +# The interval string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. 30s or 1m. +;min_refresh_interval = 5s + +# Path to the default home dashboard. If this value is empty, then Grafana uses StaticRootPath + "dashboards/home.json" +default_home_dashboard_path = /var/lib/grafana/dashboards/home.json + +#################################### Users ############################### +[users] +# disable user signup / registration +;allow_sign_up = true + +# Allow non admin users to create organizations +;allow_org_create = true + +# Set to true to automatically assign new users to the default organization (id 1) +;auto_assign_org = true + +# Set this value to automatically add new users to the provided organization (if auto_assign_org above is set to true) +;auto_assign_org_id = 1 + +# Default role new users will be automatically assigned (if disabled above is set to true) +;auto_assign_org_role = Viewer + +# Require email validation before sign up completes +;verify_email_enabled = false + +# Background text for the user field on the login page +;login_hint = email or username +;password_hint = password + +# Default UI theme ("dark" or "light") +;default_theme = dark + +# Path to a custom home page. Users are only redirected to this if the default home dashboard is used. It should match a frontend route and contain a leading slash. +; home_page = + +# External user management, these options affect the organization users view +;external_manage_link_url = +;external_manage_link_name = +;external_manage_info = + +# Viewers can edit/inspect dashboard settings in the browser. But not save the dashboard. +;viewers_can_edit = false + +# Editors can administrate dashboard, folders and teams they create +;editors_can_admin = false + +# The duration in time a user invitation remains valid before expiring. This setting should be expressed as a duration. Examples: 6h (hours), 2d (days), 1w (week). Default is 24h (24 hours). The minimum supported duration is 15m (15 minutes). +;user_invite_max_lifetime_duration = 24h + +# Enter a comma-separated list of users login to hide them in the Grafana UI. These users are shown to Grafana admins and themselves. +; hidden_users = + +[auth] +# Login cookie name +;login_cookie_name = grafana_session + +# The maximum lifetime (duration) an authenticated user can be inactive before being required to login at next visit. Default is 7 days (7d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month). The lifetime resets at each successful token rotation. +;login_maximum_inactive_lifetime_duration = + +# The maximum lifetime (duration) an authenticated user can be logged in since login time before being required to login. Default is 30 days (30d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month). +;login_maximum_lifetime_duration = + +# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes. +;token_rotation_interval_minutes = 10 + +# Set to true to disable (hide) the login form, useful if you use OAuth, defaults to false +;disable_login_form = false + +# Set to true to disable the sign out link in the side menu. Useful if you use auth.proxy or auth.jwt, defaults to false +;disable_signout_menu = false + +# URL to redirect the user to after sign out +;signout_redirect_url = + +# Set to true to attempt login with OAuth automatically, skipping the login screen. +# This setting is ignored if multiple OAuth providers are configured. +;oauth_auto_login = false + +# OAuth state max age cookie duration in seconds. Defaults to 600 seconds. +;oauth_state_cookie_max_age = 600 + +# limit of api_key seconds to live before expiration +;api_key_max_seconds_to_live = -1 + +# Set to true to enable SigV4 authentication option for HTTP-based datasources. +;sigv4_auth_enabled = false + +#################################### Anonymous Auth ###################### +[auth.anonymous] +# enable anonymous access +enabled = true + +# specify organization name that should be used for unauthenticated users +;org_name = Main Org. + +# specify role for unauthenticated users +;org_role = Viewer + +# mask the Grafana version number for unauthenticated users +;hide_version = false + +#################################### GitHub Auth ########################## +[auth.github] +;enabled = false +;allow_sign_up = true +;client_id = some_id +;client_secret = some_secret +;scopes = user:email,read:org +;auth_url = https://github.com/login/oauth/authorize +;token_url = https://github.com/login/oauth/access_token +;api_url = https://api.github.com/user +;allowed_domains = +;team_ids = +;allowed_organizations = + +#################################### GitLab Auth ######################### +[auth.gitlab] +;enabled = false +;allow_sign_up = true +;client_id = some_id +;client_secret = some_secret +;scopes = api +;auth_url = https://gitlab.com/oauth/authorize +;token_url = https://gitlab.com/oauth/token +;api_url = https://gitlab.com/api/v4 +;allowed_domains = +;allowed_groups = + +#################################### Google Auth ########################## +[auth.google] +;enabled = false +;allow_sign_up = true +;client_id = some_client_id +;client_secret = some_client_secret +;scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email +;auth_url = https://accounts.google.com/o/oauth2/auth +;token_url = https://accounts.google.com/o/oauth2/token +;api_url = https://www.googleapis.com/oauth2/v1/userinfo +;allowed_domains = +;hosted_domain = + +#################################### Grafana.com Auth #################### +[auth.grafana_com] +;enabled = false +;allow_sign_up = true +;client_id = some_id +;client_secret = some_secret +;scopes = user:email +;allowed_organizations = + +#################################### Azure AD OAuth ####################### +[auth.azuread] +;name = Azure AD +;enabled = false +;allow_sign_up = true +;client_id = some_client_id +;client_secret = some_client_secret +;scopes = openid email profile +;auth_url = https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize +;token_url = https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token +;allowed_domains = +;allowed_groups = + +#################################### Okta OAuth ####################### +[auth.okta] +;name = Okta +;enabled = false +;allow_sign_up = true +;client_id = some_id +;client_secret = some_secret +;scopes = openid profile email groups +;auth_url = https://<tenant-id>.okta.com/oauth2/v1/authorize +;token_url = https://<tenant-id>.okta.com/oauth2/v1/token +;api_url = https://<tenant-id>.okta.com/oauth2/v1/userinfo +;allowed_domains = +;allowed_groups = +;role_attribute_path = +;role_attribute_strict = false + +#################################### Generic OAuth ########################## +[auth.generic_oauth] +;enabled = false +;name = OAuth +;allow_sign_up = true +;client_id = some_id +;client_secret = some_secret +;scopes = user:email,read:org +;empty_scopes = false +;email_attribute_name = email:primary +;email_attribute_path = +;login_attribute_path = +;name_attribute_path = +;id_token_attribute_name = +;auth_url = https://foo.bar/login/oauth/authorize +;token_url = https://foo.bar/login/oauth/access_token +;api_url = https://foo.bar/user +;allowed_domains = +;team_ids = +;allowed_organizations = +;role_attribute_path = +;role_attribute_strict = false +;groups_attribute_path = +;tls_skip_verify_insecure = false +;tls_client_cert = +;tls_client_key = +;tls_client_ca = + +#################################### Basic Auth ########################## +[auth.basic] +;enabled = true + +#################################### Auth Proxy ########################## +[auth.proxy] +;enabled = false +;header_name = X-WEBAUTH-USER +;header_property = username +;auto_sign_up = true +;sync_ttl = 60 +;whitelist = 192.168.1.1, 192.168.2.1 +;headers = Email:X-User-Email, Name:X-User-Name +# Read the auth proxy docs for details on what the setting below enables +;enable_login_token = false + +#################################### Auth JWT ########################## +[auth.jwt] +;enabled = true +;header_name = X-JWT-Assertion +;email_claim = sub +;username_claim = sub +;jwk_set_url = https://foo.bar/.well-known/jwks.json +;jwk_set_file = /path/to/jwks.json +;cache_ttl = 60m +;expected_claims = {"aud": ["foo", "bar"]} +;key_file = /path/to/key/file + +#################################### Auth LDAP ########################## +[auth.ldap] +;enabled = false +;config_file = /etc/grafana/ldap.toml +;allow_sign_up = true + +# LDAP background sync (Enterprise only) +# At 1 am every day +;sync_cron = "0 0 1 * * *" +;active_sync_enabled = true + +#################################### AWS ########################### +[aws] +# Enter a comma-separated list of allowed AWS authentication providers. +# Options are: default (AWS SDK Default), keys (Access && secret key), credentials (Credentials field), ec2_iam_role (EC2 IAM Role) +; allowed_auth_providers = default,keys,credentials + +# Allow AWS users to assume a role using temporary security credentials. +# If true, assume role will be enabled for all AWS authentication providers that are specified in aws_auth_providers +; assume_role_enabled = true + +#################################### Azure ############################### +[azure] +# Azure cloud environment where Grafana is hosted +# Possible values are AzureCloud, AzureChinaCloud, AzureUSGovernment and AzureGermanCloud +# Default value is AzureCloud (i.e. public cloud) +;cloud = AzureCloud + +# Specifies whether Grafana hosted in Azure service with Managed Identity configured (e.g. Azure Virtual Machines instance) +# If enabled, the managed identity can be used for authentication of Grafana in Azure services +# Disabled by default, needs to be explicitly enabled +;managed_identity_enabled = false + +# Client ID to use for user-assigned managed identity +# Should be set for user-assigned identity and should be empty for system-assigned identity +;managed_identity_client_id = + +#################################### SMTP / Emailing ########################## +[smtp] +;enabled = false +;host = localhost:25 +;user = +# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;""" +;password = +;cert_file = +;key_file = +;skip_verify = false +;from_address = admin@grafana.localhost +;from_name = Grafana +# EHLO identity in SMTP dialog (defaults to instance_name) +;ehlo_identity = dashboard.example.com +# SMTP startTLS policy (defaults to 'OpportunisticStartTLS') +;startTLS_policy = NoStartTLS + +[emails] +;welcome_email_on_sign_up = false +;templates_pattern = emails/*.html, emails/*.txt +;content_types = text/html + +#################################### Logging ########################## +[log] +# Either "console", "file", "syslog". Default is console and file +# Use space to separate multiple modes, e.g. "console file" +;mode = console file + +# Either "debug", "info", "warn", "error", "critical", default is "info" +;level = info + +# optional settings to set different levels for specific loggers. Ex filters = sqlstore:debug +;filters = + +# For "console" mode only +[log.console] +;level = + +# log line format, valid options are text, console and json +;format = console + +# For "file" mode only +[log.file] +;level = + +# log line format, valid options are text, console and json +;format = text + +# This enables automated log rotate(switch of following options), default is true +;log_rotate = true + +# Max line number of single file, default is 1000000 +;max_lines = 1000000 + +# Max size shift of single file, default is 28 means 1 << 28, 256MB +;max_size_shift = 28 + +# Segment log daily, default is true +;daily_rotate = true + +# Expired days of log file(delete after max days), default is 7 +;max_days = 7 + +[log.syslog] +;level = + +# log line format, valid options are text, console and json +;format = text + +# Syslog network type and address. This can be udp, tcp, or unix. If left blank, the default unix endpoints will be used. +;network = +;address = + +# Syslog facility. user, daemon and local0 through local7 are valid. +;facility = + +# Syslog tag. By default, the process' argv[0] is used. +;tag = + +[log.frontend] +# Should Sentry javascript agent be initialized +;enabled = false + +# Sentry DSN if you want to send events to Sentry. +;sentry_dsn = + +# Custom HTTP endpoint to send events captured by the Sentry agent to. Default will log the events to stdout. +;custom_endpoint = /log + +# Rate of events to be reported between 0 (none) and 1 (all), float +;sample_rate = 1.0 + +# Requests per second limit enforced an extended period, for Grafana backend log ingestion endpoint (/log). +;log_endpoint_requests_per_second_limit = 3 + +# Max requests accepted per short interval of time for Grafana backend log ingestion endpoint (/log). +;log_endpoint_burst_limit = 15 + +#################################### Usage Quotas ######################## +[quota] +; enabled = false + +#### set quotas to -1 to make unlimited. #### +# limit number of users per Org. +; org_user = 10 + +# limit number of dashboards per Org. +; org_dashboard = 100 + +# limit number of data_sources per Org. +; org_data_source = 10 + +# limit number of api_keys per Org. +; org_api_key = 10 + +# limit number of alerts per Org. +;org_alert_rule = 100 + +# limit number of orgs a user can create. +; user_org = 10 + +# Global limit of users. +; global_user = -1 + +# global limit of orgs. +; global_org = -1 + +# global limit of dashboards +; global_dashboard = -1 + +# global limit of api_keys +; global_api_key = -1 + +# global limit on number of logged in users. +; global_session = -1 + +# global limit of alerts +;global_alert_rule = -1 + +#################################### Alerting ############################ +[alerting] +# Disable alerting engine & UI features +;enabled = true +# Makes it possible to turn off alert rule execution but alerting UI is visible +;execute_alerts = true + +# Default setting for new alert rules. Defaults to categorize error and timeouts as alerting. (alerting, keep_state) +;error_or_timeout = alerting + +# Default setting for how Grafana handles nodata or null values in alerting. (alerting, no_data, keep_state, ok) +;nodata_or_nullvalues = no_data + +# Alert notifications can include images, but rendering many images at the same time can overload the server +# This limit will protect the server from render overloading and make sure notifications are sent out quickly +;concurrent_render_limit = 5 + + +# Default setting for alert calculation timeout. Default value is 30 +;evaluation_timeout_seconds = 30 + +# Default setting for alert notification timeout. Default value is 30 +;notification_timeout_seconds = 30 + +# Default setting for max attempts to sending alert notifications. Default value is 3 +;max_attempts = 3 + +# Makes it possible to enforce a minimal interval between evaluations, to reduce load on the backend +;min_interval_seconds = 1 + +# Configures for how long alert annotations are stored. Default is 0, which keeps them forever. +# This setting should be expressed as a duration. Examples: 6h (hours), 10d (days), 2w (weeks), 1M (month). +;max_annotation_age = + +# Configures max number of alert annotations that Grafana stores. Default value is 0, which keeps all alert annotations. +;max_annotations_to_keep = + +#################################### Annotations ######################### +[annotations] +# Configures the batch size for the annotation clean-up job. This setting is used for dashboard, API, and alert annotations. +;cleanupjob_batchsize = 100 + +[annotations.dashboard] +# Dashboard annotations means that annotations are associated with the dashboard they are created on. + +# Configures how long dashboard annotations are stored. Default is 0, which keeps them forever. +# This setting should be expressed as a duration. Examples: 6h (hours), 10d (days), 2w (weeks), 1M (month). +;max_age = + +# Configures max number of dashboard annotations that Grafana stores. Default value is 0, which keeps all dashboard annotations. +;max_annotations_to_keep = + +[annotations.api] +# API annotations means that the annotations have been created using the API without any +# association with a dashboard. + +# Configures how long Grafana stores API annotations. Default is 0, which keeps them forever. +# This setting should be expressed as a duration. Examples: 6h (hours), 10d (days), 2w (weeks), 1M (month). +;max_age = + +# Configures max number of API annotations that Grafana keeps. Default value is 0, which keeps all API annotations. +;max_annotations_to_keep = + +#################################### Explore ############################# +[explore] +# Enable the Explore section +;enabled = true + +#################################### Internal Grafana Metrics ########################## +# Metrics available at HTTP API Url /metrics +[metrics] +# Disable / Enable internal metrics +;enabled = true +# Graphite Publish interval +;interval_seconds = 10 +# Disable total stats (stat_totals_*) metrics to be generated +;disable_total_stats = false + +#If both are set, basic auth will be required for the metrics endpoint. +; basic_auth_username = +; basic_auth_password = + +# Metrics environment info adds dimensions to the `grafana_environment_info` metric, which +# can expose more information about the Grafana instance. +[metrics.environment_info] +#exampleLabel1 = exampleValue1 +#exampleLabel2 = exampleValue2 + +# Send internal metrics to Graphite +[metrics.graphite] +# Enable by setting the address setting (ex localhost:2003) +;address = +;prefix = prod.grafana.%(instance_name)s. + +#################################### Grafana.com integration ########################## +# Url used to import dashboards directly from Grafana.com +[grafana_com] +;url = https://grafana.com + +#################################### Distributed tracing ############ +[tracing.jaeger] +# Enable by setting the address sending traces to jaeger (ex localhost:6831) +;address = localhost:6831 +# Tag that will always be included in when creating new spans. ex (tag1:value1,tag2:value2) +;always_included_tag = tag1:value1 +# Type specifies the type of the sampler: const, probabilistic, rateLimiting, or remote +;sampler_type = const +# jaeger samplerconfig param +# for "const" sampler, 0 or 1 for always false/true respectively +# for "probabilistic" sampler, a probability between 0 and 1 +# for "rateLimiting" sampler, the number of spans per second +# for "remote" sampler, param is the same as for "probabilistic" +# and indicates the initial sampling rate before the actual one +# is received from the mothership +;sampler_param = 1 +# sampling_server_url is the URL of a sampling manager providing a sampling strategy. +;sampling_server_url = +# Whether or not to use Zipkin propagation (x-b3- HTTP headers). +;zipkin_propagation = false +# Setting this to true disables shared RPC spans. +# Not disabling is the most common setting when using Zipkin elsewhere in your infrastructure. +;disable_shared_zipkin_spans = false + +#################################### External image storage ########################## +[external_image_storage] +# Used for uploading images to public servers so they can be included in slack/email messages. +# you can choose between (s3, webdav, gcs, azure_blob, local) +;provider = + +[external_image_storage.s3] +;endpoint = +;path_style_access = +;bucket = +;region = +;path = +;access_key = +;secret_key = + +[external_image_storage.webdav] +;url = +;public_url = +;username = +;password = + +[external_image_storage.gcs] +;key_file = +;bucket = +;path = + +[external_image_storage.azure_blob] +;account_name = +;account_key = +;container_name = + +[external_image_storage.local] +# does not require any configuration + +[rendering] +# Options to configure a remote HTTP image rendering service, e.g. using https://github.com/grafana/grafana-image-renderer. +# URL to a remote HTTP image renderer service, e.g. http://localhost:8081/render, will enable Grafana to render panels and dashboards to PNG-images using HTTP requests to an external service. +;server_url = +# If the remote HTTP image renderer service runs on a different server than the Grafana server you may have to configure this to a URL where Grafana is reachable, e.g. http://grafana.domain/. +;callback_url = +# Concurrent render request limit affects when the /render HTTP endpoint is used. Rendering many images at the same time can overload the server, +# which this setting can help protect against by only allowing a certain amount of concurrent requests. +;concurrent_render_request_limit = 30 + +[panels] +# If set to true Grafana will allow script tags in text panels. Not recommended as it enable XSS vulnerabilities. +;disable_sanitize_html = false + +[plugins] +;enable_alpha = false +;app_tls_skip_verify_insecure = false +# Enter a comma-separated list of plugin identifiers to identify plugins to load even if they are unsigned. Plugins with modified signatures are never loaded. +;allow_loading_unsigned_plugins = +# Enable or disable installing plugins directly from within Grafana. +;plugin_admin_enabled = false +;plugin_admin_external_manage_enabled = false +;plugin_catalog_url = https://grafana.com/grafana/plugins/ + +#################################### Grafana Live ########################################## +[live] +# max_connections to Grafana Live WebSocket endpoint per Grafana server instance. See Grafana Live docs +# if you are planning to make it higher than default 100 since this can require some OS and infrastructure +# tuning. 0 disables Live, -1 means unlimited connections. +;max_connections = 100 + +# allowed_origins is a comma-separated list of origins that can establish connection with Grafana Live. +# If not set then origin will be matched over root_url. Supports wildcard symbol "*". +;allowed_origins = + +# engine defines an HA (high availability) engine to use for Grafana Live. By default no engine used - in +# this case Live features work only on a single Grafana server. Available options: "redis". +# Setting ha_engine is an EXPERIMENTAL feature. +;ha_engine = + +# ha_engine_address sets a connection address for Live HA engine. Depending on engine type address format can differ. +# For now we only support Redis connection address in "host:port" format. +# This option is EXPERIMENTAL. +;ha_engine_address = "127.0.0.1:6379" + +#################################### Grafana Image Renderer Plugin ########################## +[plugin.grafana-image-renderer] +# Instruct headless browser instance to use a default timezone when not provided by Grafana, e.g. when rendering panel image of alert. +# See ICU’s metaZones.txt (https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt) for a list of supported +# timezone IDs. Fallbacks to TZ environment variable if not set. +;rendering_timezone = + +# Instruct headless browser instance to use a default language when not provided by Grafana, e.g. when rendering panel image of alert. +# Please refer to the HTTP header Accept-Language to understand how to format this value, e.g. 'fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5'. +;rendering_language = + +# Instruct headless browser instance to use a default device scale factor when not provided by Grafana, e.g. when rendering panel image of alert. +# Default is 1. Using a higher value will produce more detailed images (higher DPI), but will require more disk space to store an image. +;rendering_viewport_device_scale_factor = + +# Instruct headless browser instance whether to ignore HTTPS errors during navigation. Per default HTTPS errors are not ignored. Due to +# the security risk it's not recommended to ignore HTTPS errors. +;rendering_ignore_https_errors = + +# Instruct headless browser instance whether to capture and log verbose information when rendering an image. Default is false and will +# only capture and log error messages. When enabled, debug messages are captured and logged as well. +# For the verbose information to be included in the Grafana server log you have to adjust the rendering log level to debug, configure +# [log].filter = rendering:debug. +;rendering_verbose_logging = + +# Instruct headless browser instance whether to output its debug and error messages into running process of remote rendering service. +# Default is false. This can be useful to enable (true) when troubleshooting. +;rendering_dumpio = + +# Additional arguments to pass to the headless browser instance. Default is --no-sandbox. The list of Chromium flags can be found +# here (https://peter.sh/experiments/chromium-command-line-switches/). Multiple arguments is separated with comma-character. +;rendering_args = + +# You can configure the plugin to use a different browser binary instead of the pre-packaged version of Chromium. +# Please note that this is not recommended, since you may encounter problems if the installed version of Chrome/Chromium is not +# compatible with the plugin. +;rendering_chrome_bin = + +# Instruct how headless browser instances are created. Default is 'default' and will create a new browser instance on each request. +# Mode 'clustered' will make sure that only a maximum of browsers/incognito pages can execute concurrently. +# Mode 'reusable' will have one browser instance and will create a new incognito page on each request. +;rendering_mode = + +# When rendering_mode = clustered you can instruct how many browsers or incognito pages can execute concurrently. Default is 'browser' +# and will cluster using browser instances. +# Mode 'context' will cluster using incognito pages. +;rendering_clustering_mode = +# When rendering_mode = clustered you can define maximum number of browser instances/incognito pages that can execute concurrently.. +;rendering_clustering_max_concurrency = + +# Limit the maximum viewport width, height and device scale factor that can be requested. +;rendering_viewport_max_width = +;rendering_viewport_max_height = +;rendering_viewport_max_device_scale_factor = + +# Change the listening host and port of the gRPC server. Default host is 127.0.0.1 and default port is 0 and will automatically assign +# a port not in use. +;grpc_host = +;grpc_port = + +[enterprise] +# Path to a valid Grafana Enterprise license.jwt file +;license_path = + +[feature_toggles] +# enable features, separated by spaces +enable = ngalert + +[date_formats] +# For information on what formatting patterns that are supported https://momentjs.com/docs/#/displaying/ + +# Default system date format used in time range picker and other places where full time is displayed +;full_date = YYYY-MM-DD HH:mm:ss + +# Used by graph and other places where we only show small intervals +;interval_second = HH:mm:ss +;interval_minute = HH:mm +;interval_hour = MM/DD HH:mm +;interval_day = MM/DD +;interval_month = YYYY-MM +;interval_year = YYYY + +# Experimental feature +;use_browser_locale = false + +# Default timezone for user preferences. Options are 'browser' for the browser local timezone or a timezone name from IANA Time Zone database, e.g. 'UTC' or 'Europe/Amsterdam' etc. +;default_timezone = browser + +[expressions] +# Enable or disable the expressions functionality. +;enabled = true + +[geomap] +# Set the JSON configuration for the default basemap +;default_baselayer_config = `{ +; "type": "xyz", +; "config": { +; "attribution": "Open street map", +; "url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png" +; } +;}` + +# Enable or disable loading other base map layers +;enable_custom_baselayers = true diff --git a/docker-compose/grafana/stationcontrol-dashboards.yaml b/docker-compose/grafana/stationcontrol-dashboards.yaml new file mode 100644 index 0000000000000000000000000000000000000000..50d300483241f1c5c4b1c992d834bfa4d71014f6 --- /dev/null +++ b/docker-compose/grafana/stationcontrol-dashboards.yaml @@ -0,0 +1,24 @@ +apiVersion: 1 + +providers: + # <string> an unique provider name. Required + - name: 'StationControl' + # <int> Org id. Default to 1 + orgId: 1 + # <string> name of the dashboard folder. + folder: '' + # <string> folder UID. will be automatically generated if not specified + folderUid: '' + # <string> provider type. Default to 'file' + type: file + # <bool> disable dashboard deletion + disableDeletion: true + # <int> how often Grafana will scan for changed dashboards + updateIntervalSeconds: 60 + # <bool> allow updating provisioned dashboards from the UI + allowUiUpdates: false + options: + # <string, required> path to dashboard files on disk. Required when using the 'file' type + path: /var/lib/grafana/dashboards + # <bool> use folder names from filesystem to create folders in Grafana + foldersFromFilesStructure: true diff --git a/docker-compose/hdbpp_viewer.yml b/docker-compose/hdbpp_viewer.yml index 481879729621d5c5828abddbbd047182d9b16278..9a1f9da06a3db1398e3f4060fd32de9850e6532e 100644 --- a/docker-compose/hdbpp_viewer.yml +++ b/docker-compose/hdbpp_viewer.yml @@ -12,8 +12,7 @@ services: hdbpp-viewer: image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/hdbpp_viewer:${TANGO_HDBPP_VIEWER_VERSION} container_name: ${CONTAINER_NAME_PREFIX}hdbpp-viewer - networks: - - control + network_mode: host depends_on: - databaseds - dsconfig @@ -25,17 +24,17 @@ services: environment: - XAUTHORITY=${XAUTHORITY} - DISPLAY=${DISPLAY} - - TANGO_HOST=${TANGO_HOST} + - TANGO_HOST=localhost:10000 - HDB_TYPE=mysql - - HDB_MYSQL_HOST=archiver-maria-db - - HDB_MYSQL_PORT=3306 + - HDB_MYSQL_HOST=localhost + - HDB_MYSQL_PORT=3307 - HDB_USER=tango - HDB_PASSWORD=tango - HDB_NAME=hdbpp - CLASSPATH=JTango.jar:ATKCore.jar:ATKWidget.jar:jhdbviewer.jar:HDBPP.jar:jython.jar:jcalendar.jar entrypoint: - wait-for-it.sh - - ${TANGO_HOST} + - localhost:10000 - --strict - -- - ./hdbpp_viewer/hdbpp_viewer_script diff --git a/docker-compose/integration-test.yml b/docker-compose/integration-test.yml new file mode 100644 index 0000000000000000000000000000000000000000..defb45e3c3183516131795b283372ca784635d8c --- /dev/null +++ b/docker-compose/integration-test.yml @@ -0,0 +1,31 @@ +# +# Docker compose file that launches integration tests +# +# Defines: +# - integration: Integration testing +# +version: '2' + +services: + integration-test: + build: + context: itango + args: + SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION} + container_name: ${CONTAINER_NAME_PREFIX}integration-test + networks: + - control + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ..:/opt/lofar/tango:rw + environment: + - TANGO_HOST=${TANGO_HOST} + working_dir: /opt/lofar/tango/tangostationcontrol + entrypoint: + - /usr/local/bin/wait-for-it.sh + - ${TANGO_HOST} + - --timeout=30 + - --strict + - -- + - tox --recreate -e integration diff --git a/docker-compose/itango.yml b/docker-compose/itango.yml index 941974e8770823c6a680de9ffcf95f301163663d..9b01c4ea25e2abc5849c9a98c29cc7601ba1115f 100644 --- a/docker-compose/itango.yml +++ b/docker-compose/itango.yml @@ -21,8 +21,10 @@ services: container_name: ${CONTAINER_NAME_PREFIX}itango networks: - control + extra_hosts: + - "host.docker.internal:host-gateway" volumes: - - ${TANGO_LOFAR_CONTAINER_MOUNT} + - ..:/opt/lofar/tango:rw - ${HOME}:/hosthome environment: - TANGO_HOST=${TANGO_HOST} @@ -37,4 +39,4 @@ services: - --strict - -- - /venv/bin/itango3 - restart: on-failure + restart: unless-stopped diff --git a/docker-compose/itango/lofar-requirements.txt b/docker-compose/itango/lofar-requirements.txt index 0e869add1a8113a1f63f84e9348321dad5a5c4f2..b193887dbfdb59a4bc143ce6dd48a720ec5ad00c 100644 --- a/docker-compose/itango/lofar-requirements.txt +++ b/docker-compose/itango/lofar-requirements.txt @@ -1,8 +1,4 @@ +# Do not put tangostationcontrol dependencies here parso == 0.7.1 jedi == 0.17.2 -opcua >= 0.98.13 -astropy -python-logstash-async -gitpython -PyMySQL[rsa] -sqlalchemy +astropy diff --git a/docker-compose/jive.yml b/docker-compose/jive.yml index b810073e6a2ddf691b88a47c2d805331605379e8..456ae1fc96771bad1ab6b99e52e3b0c9c046c20c 100644 --- a/docker-compose/jive.yml +++ b/docker-compose/jive.yml @@ -23,7 +23,7 @@ services: network_mode: host volumes: - ${XAUTHORITY_MOUNT} - - ${TANGO_LOFAR_CONTAINER_MOUNT} + - ..:/opt/lofar/tango:rw - ${HOME}:/hosthome environment: - XAUTHORITY=${XAUTHORITY} diff --git a/docker-compose/jupyter.yml b/docker-compose/jupyter.yml index 36cc0acbcd32631a9cf8e6bb1f10ecfb77362cf0..1e1deea6f0e22299544f988602efc676bbe6200c 100644 --- a/docker-compose/jupyter.yml +++ b/docker-compose/jupyter.yml @@ -20,13 +20,11 @@ services: networks: - control volumes: - - ${TANGO_LOFAR_CONTAINER_MOUNT} - - ${TANGO_LOFAR_LOCAL_DIR}/jupyter-notebooks:/jupyter-notebooks:rw + - ..:/opt/lofar/tango:rw + - ../jupyter-notebooks:/jupyter-notebooks:rw - ${HOME}:/hosthome environment: - TANGO_HOST=${TANGO_HOST} - - XAUTHORITY=${XAUTHORITY} - - DISPLAY=${DISPLAY} ports: - "8888:8888" user: ${CONTAINER_EXECUTION_UID} @@ -38,4 +36,4 @@ services: - --strict - -- - /usr/bin/tini -- /usr/local/bin/jupyter-notebook --port=8888 --no-browser --ip=0.0.0.0 --allow-root --NotebookApp.token= --NotebookApp.password= - restart: on-failure + restart: unless-stopped diff --git a/docker-compose/jupyter/Dockerfile b/docker-compose/jupyter/Dockerfile index 2382319bc1a26e4e9f75b4ee8bdb45c893d23528..5393cece6a74ff1de85e9c37ce6a8307e3a66cf5 100644 --- a/docker-compose/jupyter/Dockerfile +++ b/docker-compose/jupyter/Dockerfile @@ -5,12 +5,32 @@ FROM ${SOURCE_IMAGE} # that are needed for temporary storage the proper owner and access rights. ARG CONTAINER_EXECUTION_UID=1000 +# Create homedir +ENV HOME=/home/user +RUN sudo mkdir -p ${HOME} +RUN sudo chown ${CONTAINER_EXECUTION_UID} -R ${HOME} + +# ipython 7.28 is broken in combination with Jupyter, it causes connection errors with notebooks +RUN sudo pip3 install ipython==7.27.0 + RUN sudo pip3 install jupyter RUN sudo pip3 install ipykernel RUN sudo pip3 install jupyter_bokeh # Install matplotlib, jupyterplot RUN sudo pip3 install matplotlib jupyterplot +# Allow Download as -> PDF via html +RUN sudo pip3 install nbconvert +RUN sudo pip3 install notebook-as-pdf + +# see https://github.com/jupyter/nbconvert/issues/1434 +RUN sudo bash -c "echo DEFAULT_ARGS += [\\\"--no-sandbox\\\"] >> /usr/local/lib/python3.7/dist-packages/pyppeteer/launcher.py" +RUN sudo apt-get update -y +RUN sudo apt-get install -y gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget libcairo-gobject2 libxinerama1 libgtk2.0-0 libpangoft2-1.0-0 libthai0 libpixman-1-0 libxcb-render0 libharfbuzz0b libdatrie1 libgraphite2-3 libgbm1 + +# Allow Download as -> PDF via LaTeX +RUN sudo apt-get install -y texlive-xetex texlive-fonts-recommended texlive-latex-recommended + # Configure jupyter_bokeh RUN sudo mkdir -p /usr/share/jupyter /usr/etc RUN sudo chmod a+rwx /usr/share/jupyter /usr/etc @@ -19,6 +39,7 @@ RUN sudo jupyter nbextension enable jupyter_bokeh --py --sys-prefix # Install profiles for ipython & jupyter COPY ipython-profiles /opt/ipython-profiles/ +RUN sudo chown ${CONTAINER_EXECUTION_UID} -R /opt/ipython-profiles COPY jupyter-kernels /usr/local/share/jupyter/kernels/ # Install patched jupyter executable @@ -28,14 +49,15 @@ COPY jupyter-notebook /usr/local/bin/jupyter-notebook #Install further python modules RUN sudo pip3 install PyMySQL[rsa] sqlalchemy +# Packages to interface with testing hardware directly +RUN sudo pip3 install pyvisa pyvisa-py opcua + # Add Tini. Tini operates as a process subreaper for jupyter. This prevents kernel crashes. ENV TINI_VERSION v0.6.0 ENV JUPYTER_RUNTIME_DIR=/tmp ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /usr/bin/tini RUN sudo chmod +x /usr/bin/tini -# Make sure Jupyter can write to the home directory -ENV HOME=/home/user -RUN sudo mkdir -p ${HOME} -RUN sudo chown ${CONTAINER_EXECUTION_UID} -R ${HOME} -RUN sudo chown ${CONTAINER_EXECUTION_UID} -R /opt/ipython-profiles +USER ${CONTAINER_EXECUTION_UID} +# pyppeteer-install installs in the homedir, so run it as the user that will execute the notebook +RUN pyppeteer-install diff --git a/docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py b/docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py index bc56f8b05de5e90804562bcf77378ae8798100a2..74692f10fff62684c21047eb373aebd70a876155 100644 --- a/docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py +++ b/docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py @@ -1,7 +1,13 @@ # Create shortcuts for our devices -pcc = DeviceProxy("LTS/PCC/1") -sdp = DeviceProxy("LTS/SDP/1") -sst = DeviceProxy("LTS/SST/1") +apsct = DeviceProxy("STAT/APSCT/1") +apspu = DeviceProxy("STAT/APSPU/1") +recv = DeviceProxy("STAT/RECV/1") +sdp = DeviceProxy("STAT/SDP/1") +sst = DeviceProxy("STAT/SST/1") +xst = DeviceProxy("STAT/XST/1") +unb2 = DeviceProxy("STAT/UNB2/1") +boot = DeviceProxy("STAT/Boot/1") +docker = DeviceProxy("STAT/Docker/1") # Put them in a list in case one wants to iterate -devices = [pcc, sdp, sst] +devices = [apsct, apspu, recv, sdp, sst, xst, unb2, boot, docker] diff --git a/docker-compose/lofar-device-base.yml b/docker-compose/lofar-device-base.yml index 22ecabca37c526867afe52461d7ce432964f8385..ce110ed85ba0cfb20b607ab7d08e70505d2392e8 100644 --- a/docker-compose/lofar-device-base.yml +++ b/docker-compose/lofar-device-base.yml @@ -20,5 +20,9 @@ services: args: SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION} container_name: ${CONTAINER_NAME_PREFIX}lofar-device-base + # These parameters are just visual queues, you have to define them again + # in derived docker-compose files! networks: - control + extra_hosts: + - "host.docker.internal:host-gateway" diff --git a/docker-compose/lofar-device-base/lofar-requirements.txt b/docker-compose/lofar-device-base/lofar-requirements.txt index 69d52984a264c3a53bbcfece15be810ccaa32e7b..10ad55d977c97793a352c13da323d84d3c826c0e 100644 --- a/docker-compose/lofar-device-base/lofar-requirements.txt +++ b/docker-compose/lofar-device-base/lofar-requirements.txt @@ -1,4 +1,5 @@ -opcua >= 0.98.9 +# Do not put tangostationcontrol dependencies here astropy -python-logstash-async -gitpython + +# requirements to build tangocontrol +GitPython >= 3.1.24 # BSD diff --git a/docker-compose/pogo.yml b/docker-compose/pogo.yml index 2029874a5247ead93054a6f83e8185ea01be2487..826daac9fbd6ef3226a690832eedab505bbeaba3 100644 --- a/docker-compose/pogo.yml +++ b/docker-compose/pogo.yml @@ -27,7 +27,7 @@ services: volumes: - pogo:/home/tango - ${XAUTHORITY_MOUNT} - - ${TANGO_LOFAR_CONTAINER_MOUNT} + - ..:/opt/lofar/tango:rw - ${HOME}:/hosthome:rw environment: - XAUTHORITY=${XAUTHORITY} diff --git a/docker-compose/prometheus.yml b/docker-compose/prometheus.yml new file mode 100644 index 0000000000000000000000000000000000000000..604f4bf4bde93dd6d68aaf7f3b1da2fd3f884e83 --- /dev/null +++ b/docker-compose/prometheus.yml @@ -0,0 +1,25 @@ +# +# Docker compose file that launches Prometheus +# +# Defines: +# - prometheus: Prometheus +# +version: '2' + +services: + prometheus: + image: prometheus + build: + context: prometheus + container_name: ${CONTAINER_NAME_PREFIX}prometheus + networks: + - control + ports: + - "9090:9090" + logging: + driver: syslog + options: + syslog-address: udp://${LOG_HOSTNAME}:1514 + syslog-format: rfc3164 + tag: "{{.Name}}" + restart: unless-stopped diff --git a/docker-compose/prometheus/Dockerfile b/docker-compose/prometheus/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..cc1494f98dbce6c66e437b001af2a88320ca0ffa --- /dev/null +++ b/docker-compose/prometheus/Dockerfile @@ -0,0 +1,3 @@ +FROM prom/prometheus + +COPY prometheus.yml /etc/prometheus/prometheus.yml diff --git a/docker-compose/prometheus/prometheus.yml b/docker-compose/prometheus/prometheus.yml new file mode 100644 index 0000000000000000000000000000000000000000..ac9c549be45d6aab48f585dd6ab234cfc1f15449 --- /dev/null +++ b/docker-compose/prometheus/prometheus.yml @@ -0,0 +1,11 @@ +global: + evaluation_interval: 10s + scrape_interval: 10s + scrape_timeout: 10s + +scrape_configs: + - job_name: tango + static_configs: + - targets: + - "tango-prometheus-exporter:8000" + diff --git a/docker-compose/pypcc-sim/Dockerfile b/docker-compose/pypcc-sim-base/Dockerfile similarity index 58% rename from docker-compose/pypcc-sim/Dockerfile rename to docker-compose/pypcc-sim-base/Dockerfile index 4040339b1dc98ebe8e02b51c6b3b75aab55bb3d4..c65c5b6f836e889f9b3c364ceace5f7b9b821628 100644 --- a/docker-compose/pypcc-sim/Dockerfile +++ b/docker-compose/pypcc-sim-base/Dockerfile @@ -1,8 +1,10 @@ FROM ubuntu:20.04 +COPY requirements.txt /requirements.txt + RUN apt-get update && apt-get install -y python3 python3-pip python3-yaml git && \ - pip3 install opcua numpy recordclass && \ + pip3 install -r requirements.txt && \ git clone --depth 1 --branch master https://git.astron.nl/lofar2.0/pypcc WORKDIR /pypcc -CMD ["python3","pypcc2.py","--simulator"] +CMD ["python3","pypcc2.py","--simulator","--port","4843"] diff --git a/docker-compose/pypcc-sim-base/requirements.txt b/docker-compose/pypcc-sim-base/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..2cd015945c044fcd1e39a823f49a807fc519ac67 --- /dev/null +++ b/docker-compose/pypcc-sim-base/requirements.txt @@ -0,0 +1,3 @@ +opcua +numpy +recordclass>=0.16,<0.16.1 \ No newline at end of file diff --git a/docker-compose/pypcc-sim.yml b/docker-compose/pypcc-sim.yml deleted file mode 100644 index 15739d3f4dcfada169a4bb6f9ee568da612d2259..0000000000000000000000000000000000000000 --- a/docker-compose/pypcc-sim.yml +++ /dev/null @@ -1,20 +0,0 @@ -# -# Docker compose file that launches a PyPCC simulator -# -# Defines: -# - pypcc-sim -# -version: '2' - -services: - pypcc-sim: - build: - context: pypcc-sim - container_name: ${CONTAINER_NAME_PREFIX}pypcc-sim - networks: - - control - volumes: - - ${HOME}:/hosthome - ports: - - "4842:4842" - restart: on-failure diff --git a/docker-compose/recv-sim.yml b/docker-compose/recv-sim.yml new file mode 100644 index 0000000000000000000000000000000000000000..effee8b298b855e2007e50c379fa3df45010bd05 --- /dev/null +++ b/docker-compose/recv-sim.yml @@ -0,0 +1,17 @@ +# +# Docker compose file that launches a RECV simulator +# +# Defines: +# - recv-sim +# +version: '2' + +services: + recv-sim: + build: + context: pypcc-sim-base + container_name: ${CONTAINER_NAME_PREFIX}recv-sim + networks: + - control + entrypoint: python3 pypcc2.py --simulator --port 4840 --config RECVTR + restart: on-failure diff --git a/docker-compose/rest.yml b/docker-compose/rest.yml index b76ed39c5319b10403a93db6736ce8d640380efc..467319399d6f3fec12a74068fea182195014b59e 100644 --- a/docker-compose/rest.yml +++ b/docker-compose/rest.yml @@ -33,3 +33,10 @@ services: - /usr/bin/supervisord - --configuration - /etc/supervisor/supervisord.conf + logging: + driver: syslog + options: + syslog-address: udp://${LOG_HOSTNAME}:1514 + syslog-format: rfc3164 + tag: "{{.Name}}" + restart: unless-stopped diff --git a/docker-compose/sdptr-sim.yml b/docker-compose/sdptr-sim.yml index 70c1edf63acdf84df8a7d294aa17ea9489c9b9a7..c81c3db9ae4744e013b5a92f1ebb5e9bdaa6e92c 100644 --- a/docker-compose/sdptr-sim.yml +++ b/docker-compose/sdptr-sim.yml @@ -13,6 +13,4 @@ services: container_name: ${CONTAINER_NAME_PREFIX}sdptr-sim networks: - control - ports: - - "4840:4840" - restart: on-failure + restart: unless-stopped diff --git a/docker-compose/sdptr-sim/Dockerfile b/docker-compose/sdptr-sim/Dockerfile index ed6ac8d35059fda67231a0dc17c71c3a5983b13c..57fe98141f180a4d15a1e2d87c2c67be8f5894ff 100644 --- a/docker-compose/sdptr-sim/Dockerfile +++ b/docker-compose/sdptr-sim/Dockerfile @@ -16,5 +16,7 @@ RUN cd /sdptr && \ ./configure && \ bash -c "make -j `nproc` install" +COPY simulator.conf /sdptr/src/simulator.conf + WORKDIR /sdptr/src -CMD ["sdptr", "--configfile=uniboard.conf", "--nodaemon"] +CMD ["sdptr", "--type=simulator", "--configfile=simulator.conf", "--nodaemon"] diff --git a/docker-compose/sdptr-sim/simulator.conf b/docker-compose/sdptr-sim/simulator.conf new file mode 100644 index 0000000000000000000000000000000000000000..5ad69a8aed4807b815eeea18993d2b06e747b29f --- /dev/null +++ b/docker-compose/sdptr-sim/simulator.conf @@ -0,0 +1,19 @@ +# sdptr.conf +# configuration file for the SDP Translator. +# +# this config file holds settings for all [type]. +# +# # settings per type +# [LB_CORE] # [ant_band_station_type] +# n_fpgas = 16 # 8 or 16 +# first_pfga_nr = 0 # 0 for LB or 16 for HB +# ip_prefix = 10.99. # first part of ip (last part is hardware dependent) +# n_beamsets = 1 # 1 for 'LB', 'HB Remote' and 'HB International' and 2 for 'HB Core' + + +[simulator] +n_fpgas = 16 +first_fpga_nr = 0 +ip_prefix = 127.0. +n_beamsets = 1 + diff --git a/docker-compose/tango-prometheus-exporter.yml b/docker-compose/tango-prometheus-exporter.yml new file mode 100644 index 0000000000000000000000000000000000000000..bc43a6777b5595a9d94c13e55322a7adc0a8d84f --- /dev/null +++ b/docker-compose/tango-prometheus-exporter.yml @@ -0,0 +1,19 @@ +# +# Docker compose file that launches the Tango -> Prometheus adapter +# +version: '2' + +services: + tango-prometheus-exporter: + build: + context: tango-prometheus-exporter + container_name: ${CONTAINER_NAME_PREFIX}tango-prometheus-exporter + networks: + - control + environment: + - TANGO_HOST=${TANGO_HOST} + ports: + - "8000:8000" + depends_on: + - databaseds + restart: unless-stopped diff --git a/docker-compose/tango-prometheus-exporter/Dockerfile b/docker-compose/tango-prometheus-exporter/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..1df83afa690c008f83868c1bc9c8d6c1a09323ef --- /dev/null +++ b/docker-compose/tango-prometheus-exporter/Dockerfile @@ -0,0 +1,15 @@ +FROM tangocs/tango-pytango + +USER root + +RUN apt-get update && apt-get install curl -y + +USER tango + +ADD ska-tango-grafana-exporter/exporter/code /code +RUN pip install -r /code/pip-requirements.txt + +WORKDIR /code +ENV PYTHONPATH '/code/' + +CMD ["python", "-u", "/code/collector.py"] diff --git a/docker-compose/tango-prometheus-exporter/LICENSE b/docker-compose/tango-prometheus-exporter/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..5e3270dc828a392391e2e6e8fac4e1a760d34b6a --- /dev/null +++ b/docker-compose/tango-prometheus-exporter/LICENSE @@ -0,0 +1,27 @@ +Copyright 2020 INAF Matteo Di Carlo + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from this +software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/docker-compose/tango-prometheus-exporter/Makefile b/docker-compose/tango-prometheus-exporter/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..6f318981c2f1f3e28ec2dbcd856dd15cffe21116 --- /dev/null +++ b/docker-compose/tango-prometheus-exporter/Makefile @@ -0,0 +1,6 @@ +NAME:=tango-exporter + +VERSION:=1.0.2 +TAG:=$(VERSION) + +include ../make/Makefile.mk \ No newline at end of file diff --git a/docker-compose/tango-prometheus-exporter/README b/docker-compose/tango-prometheus-exporter/README new file mode 100644 index 0000000000000000000000000000000000000000..62ee6fc30cc4f0cac48f29ddd0d36e5ebea3ca8b --- /dev/null +++ b/docker-compose/tango-prometheus-exporter/README @@ -0,0 +1 @@ +Source: https://gitlab.com/ska-telescope/TANGO-grafana/-/tree/master/ diff --git a/docker-compose/tango-prometheus-exporter/get_metrics.sh b/docker-compose/tango-prometheus-exporter/get_metrics.sh new file mode 100755 index 0000000000000000000000000000000000000000..0401a2564fbaf5e71c4b8c8ff971ea2f08fe62d2 --- /dev/null +++ b/docker-compose/tango-prometheus-exporter/get_metrics.sh @@ -0,0 +1 @@ +curl $(kubectl get svc -n tango-grafana -o jsonpath='{.items[?(@.metadata.name=="tango-exporter-service-0")].spec.clusterIP}')/metrics diff --git a/docker-compose/tango-prometheus-exporter/ska-tango-grafana-exporter b/docker-compose/tango-prometheus-exporter/ska-tango-grafana-exporter new file mode 160000 index 0000000000000000000000000000000000000000..dddb23ff587f6e9c837cdb77e7955e94272eca6f --- /dev/null +++ b/docker-compose/tango-prometheus-exporter/ska-tango-grafana-exporter @@ -0,0 +1 @@ +Subproject commit dddb23ff587f6e9c837cdb77e7955e94272eca6f diff --git a/docker-compose/tango.yml b/docker-compose/tango.yml index b3a860d7b55ea71c5e2bc23895b7b57040e3f216..937cc5c8ecbe00b553d4692988e6cc2e5d7c51ef 100644 --- a/docker-compose/tango.yml +++ b/docker-compose/tango.yml @@ -28,7 +28,13 @@ services: - tangodb:/var/lib/mysql ports: - "3306:3306" - restart: on-failure + logging: + driver: syslog + options: + syslog-address: udp://${LOG_HOSTNAME}:1514 + syslog-format: rfc3164 + tag: "{{.Name}}" + restart: unless-stopped databaseds: image: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-cpp:${TANGO_CPP_VERSION} @@ -55,4 +61,10 @@ services: - "2" - -ORBendPoint - giop:tcp::10000 - restart: on-failure + logging: + driver: syslog + options: + syslog-address: udp://${LOG_HOSTNAME}:1514 + syslog-format: rfc3164 + tag: "{{.Name}}" + restart: unless-stopped diff --git a/docker-compose/tangotest.yml b/docker-compose/tangotest.yml index 3a44fc61b73b18a78d9e5bbd6a6fef6ac2d648fd..357c91df487b51379db221f7cb984bc05018f5e3 100644 --- a/docker-compose/tangotest.yml +++ b/docker-compose/tangotest.yml @@ -25,5 +25,5 @@ services: - -- - /usr/local/bin/TangoTest - test - restart: on-failure + restart: unless-stopped diff --git a/docker-compose/unb2-sim.yml b/docker-compose/unb2-sim.yml new file mode 100644 index 0000000000000000000000000000000000000000..d1ecaaa70a3c1e52f39ab1453d2ec8eb191f8831 --- /dev/null +++ b/docker-compose/unb2-sim.yml @@ -0,0 +1,17 @@ +# +# Docker compose file that launches a UNB2 simulator +# +# Defines: +# - unb2-sim +# +version: '2' + +services: + unb2-sim: + build: + context: pypcc-sim-base + container_name: ${CONTAINER_NAME_PREFIX}unb2-sim + networks: + - control + entrypoint: python3 pypcc2.py --simulator --port 4841 --config UNB2 + restart: on-failure diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..d0c3cbf1020d5c292abdedf27627c6abe25e2293 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d49db9ded07b3dbeb1087b90b99367a465d169fe --- /dev/null +++ b/docs/README.md @@ -0,0 +1,9 @@ +To build the sphinx documentation, run: + +``` +pip3 install sphinx sphinx-rtd-theme + +make html +``` + +After which the documentation will be available in html format in the `build/html` directory. diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000000000000000000000000000000000000..cf6f1dea2270d3d372ae1fa1d7a5abc136d6d343 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,52 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +#import os +#import sys +#sys.path.insert(0, os.path.abspath('../../devices')) + + +# -- Project information ----------------------------------------------------- + +project = 'LOFAR2.0 Station Control' +copyright = '2021, Stichting ASTRON' +author = 'Stichting ASTRON' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/docs/source/configure_station.rst b/docs/source/configure_station.rst new file mode 100644 index 0000000000000000000000000000000000000000..0796d098044b64cf22e8984a450f3f51752e5f0b --- /dev/null +++ b/docs/source/configure_station.rst @@ -0,0 +1,76 @@ +Enter your LOFAR2.0 Hardware Configuration +=========================================== + +The software will need to be told various aspects of your station configuration, for example, the hostnames of the station hardware to control. The following settings are installation specific, and are stored as *properties* in the :ref:`tangodb`. + +Stock configurations are provided for several stations, as well as using simulators to simulate the station's interface (which is the default after bootstrapping a station). These are provided in the ``CDB/stations/`` directory, and can be loaded using for example:: + + sbin/update_ConfigDb.sh CDB/stations/LTS_ConfigDb.json + +The following sections describe the settings that are station dependent, and thus must or can be set. + +Mandatory settings +------------------- + +Without these settings, you will not obtain the associated functionality: + +:RECV.OPC_Server_Name: Hostname of RECVTR. + + :type: ``string`` + +:UNB2.OPC_Server_Name: Hostname of UNB2TR. + + :type: ``string`` + +:SDP.OPC_Server_Name: Hostname of SDPTR. + + :type: ``string`` + +:SST.OPC_Server_Name: Hostname of SDPTR. + + :type: ``string`` + +:SST.FPGA_sst_offload_hdr_eth_destination_mac_RW_default: MAC address of the network interface on the host running this software stack, on which the SSTs are to be received. This network interface must be capable of receiving Jumbo (MTU=9000) frames. + + :type: ``string[N_fpgas]`` + +:SST.FPGA_sst_offload_hdr_ip_destination_address_RW_default: IP address of the network interface on the host running this software stack, on which the SSTs are to be received. + + :type: ``string[N_fpgas]`` + +:XST.OPC_Server_Name: Hostname of SDPTR. + + :type: ``string`` + +:XST.FPGA_xst_offload_hdr_eth_destination_mac_RW_default: MAC address of the network interface on the host running this software stack, on which the XSTs are to be received. This network interface must be capable of receiving Jumbo (MTU=9000) frames. + + :type: ``string[N_fpgas]`` + +:XST.FPGA_xst_offload_hdr_ip_destination_address_RW_default: IP address of the network interface on the host running this software stack, on which the XSTs are to be received. + + :type: ``string[N_fpgas]`` + +Optional settings +------------------- + +These settings make life nicer, but are not strictly necessary to get your software up and running: + +:RECV.Ant_mask_RW_default: Which antennas are installed. + + :type: ``bool[N_RCUs][N_antennas_per_RCU]`` + +:SDP.RCU_mask_RW_default: Which RCUs are installed. + + :type: ``bool[N_RCUs]`` + +:UNB2.UNB2_mask_RW_default: Which Uniboard2s are installed in SDP. + + :type: ``bool[N_unb]`` + +:SDP.TR_fpga_mask_RW_default: Which FPGAs are installed in SDP. + + :type: ``bool[N_fpgas]`` + +:SDP.FPGA_sdp_info_station_id_RW_default: Numeric identifier for this station. + + :type: ``uint32[N_fpgas]`` diff --git a/docs/source/developer.rst b/docs/source/developer.rst new file mode 100644 index 0000000000000000000000000000000000000000..e2c36f83c2c458628038803506da62765b8f7004 --- /dev/null +++ b/docs/source/developer.rst @@ -0,0 +1,98 @@ +Developer information +========================= + +This chapter describes key areas useful for developers. + +Docker compose +------------------------- + +The docker setup is managed using ``make`` in the ``docker-compose`` directory. Key commands are: + +- ``make status`` to check which containers are running, +- ``make build <container>`` to rebuild the image for the container, +- ``make build-nocache <container>`` to rebuild the image for the container from scratch, +- ``make restart <container>`` to restart a specific container, for example to effectuate a code change. +- ``make clean`` to remove all images and containers, and the ``tangodb`` volume. To do a deeper clean, we need to remove all volumes and rebuild all containers from scratch:: + + make clean + docker volume prune + docker build-nocache + +Since the *Python code is taken from the host when the container starts*, restarting is enough to use the code you have in your local git repo. Rebuilding is unnecessary. + +Docker networking +------------------------- + +The Docker containers started use a *virtual network* to communicate among each other. This means that: + +- Containers address each other by a host name equal to the container name (f.e. ``elk`` for the elk stack, and ``databaseds`` for the TANGO_HOST), +- ``localhost`` cannot be used within the containers to access ports of other containers. +- ``host.docker.internal`` resolves to the actual host running the containers, +- All ports used by external parties need to be exposed explicitly in the docker-compose files. The container must open the same port as is thus exposed, or the port will not be reachable. + +The networks are defined in ``docker-compose/networks.yml``: + +.. literalinclude:: ../../docker-compose/networks.yml + +The ``$NETWORK_MODE`` defaults to ``tangonet`` in the ``docker-compose/Makefile``. + +.. _corba: + +CORBA +```````````````````` + +Tango devices use CORBA, which require all servers to be able to reach each other directly. Each CORBA device opens a port and advertises its address to the CORBA broker. The broker then forwards this address to any interested clients. A device within a docker container cannot know under which name it can be reached, however, and any port opened needs to be exposed explicitly in the docker-compose file for the device. To solve all this, we *assign a unique port to each device*, and explictly tell CORBA to use that port, and what the hostname is under which others can reach it. Each device thus has these lines in their compose file:: + + ports: + - "5701:5701" # unique port for this DS + entrypoint: + # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA + # can't know about our Docker port forwarding + - python3 -u /opt/lofar/tango/devices/devices/sdp/sdp.py STAT -v -ORBendPoint giop:tcp:0:5701 -ORBendPointPublish giop:tcp:${HOSTNAME}:5701 + +Specifying the wrong ``$HOSTNAME`` or port can make your device unreachable, even if it is running. Note that ``$HOSTNAME`` is advertised as is, that is, it is resolved to an IP address by any client that wants to connect. This means the ``$HOSTNAME`` needs to be correct for both the other containers, and external clients. + +The ``docker-compose/Makefile`` tries to set a good default for ``$HOSTNAME``, but you can override it by exporting the environment variable yourself (and run ``make restart <container>`` to effectuate the change). + +For more information, see: + +- https://huihoo.org/ace_tao/ACE-5.2+TAO-1.2/TAO/docs/ORBEndpoint.html +- http://omniorb.sourceforge.net/omni42/omniNames.html +- https://sourceforge.net/p/omniorb/svn/HEAD/tree/trunk/omniORB/src/lib/omniORB/orbcore/tcp/tcpEndpoint.cc + +Logging +------------------------- + +The ELK stack collects the logs from the containers, as well as any external processes that send theirs. It is the *Logstash* part of ELK that is responsible for this. The following interfaces are available for this purpose: + ++-------------+------------+-------------------------------------------------------------------------------------------------------------+ +| Interface | Port | Note | ++=============+============+=============================================================================================================+ +| Syslog | 1514/udp | Recommended over TCP, as the ELK stack might be down. | ++-------------+------------+-------------------------------------------------------------------------------------------------------------+ +| Syslog | 1514/tcp | | ++-------------+------------+-------------------------------------------------------------------------------------------------------------+ +| JSON | 5959/tcp | From python, recommended is the `LogStash Async <https://pypi.org/project/python-logstash-async/>`_ module. | ++-------------+------------+-------------------------------------------------------------------------------------------------------------+ +| Beats | 5044/tcp | Use `FileBeat <https://www.elastic.co/beats/filebeat>`_ to watch logs locally, and forward them to ELK. | ++-------------+------------+-------------------------------------------------------------------------------------------------------------+ + +We recommend making sure the contents of your log lines are parsed correctly, especially if logs are routed to the *Syslog* input. These configurations are stored in ``docker-compose/elk/logstash/conf.d``. An example: + +.. literalinclude:: ../../docker-compose/elk/logstash/conf.d/22-parse-tango-rest.conf + +Log from Python +````````````````` + +The ``common.lofar_logging`` module provides an easy way to log to the ELK stack from a Python Tango device. + +Log from Docker +````````````````` + +Not all Docker containers run our Python programs, and can forward the logs themselves. For those, we use the ``syslog`` log driver in Docker. Extend the ``docker compose`` files with: + +.. literalinclude:: ../../docker-compose/rest.yml + :start-at: logging: + :end-before: restart: + +Logs forwarded in this way are provided with the container name, their timestamp, and a log level guessed by Docker. It is thus wise to parse the message content further in Logstash (see above). diff --git a/docs/source/devices/boot.rst b/docs/source/devices/boot.rst new file mode 100644 index 0000000000000000000000000000000000000000..45af638d95ac12ac7f3236a5f0b126a4777714ad --- /dev/null +++ b/docs/source/devices/boot.rst @@ -0,0 +1,26 @@ +.. _boot: + +Boot +==================== + +The ``boot == DeviceProxy("STAT/Boot/1")`` device is responsible for (re)starting and initialising the other devices. Devices which are not reachable, for example because their docker container is explicitly stopped, are skipped during initialisation. This device provides the following commands: + +:initialise_station(): Stop and start the other devices in the correct order, set their default values, and command them to initialise their hardware. This procedure runs asynchronously, causing this command to return immediately. Initialisation is aborted if an error is encountered. + + :returns: ``None`` + +The initialisation process can subsequently be followed through monitoring the following attributes: + +:initialising_R: Whether the initialisation procedure is still ongoing. + + :type: ``bool`` + +:initialisation_progress_R: Percentage completeness of the initialisation procedure. Each succesfully configured device increments progress. + + :type: ``int`` + +:initialisation_status_R: A description of what the device is currently trying to do. If an error occurs, this will hint towards the cause. + + :type: ``str`` + +A useful pattern is thus to call ``initialise_station()``, wait for ``initialising_R == False``, and then check whether the initalisation was succesful, if ``initialisation_progress_R == 100``. If a device fails to initialise, most likely the :doc:`../interfaces/logs` will need to be consulted. diff --git a/docs/source/devices/configure.rst b/docs/source/devices/configure.rst new file mode 100644 index 0000000000000000000000000000000000000000..074cc4776b11bfef1b1a8908aca64eb3e060b3c5 --- /dev/null +++ b/docs/source/devices/configure.rst @@ -0,0 +1,63 @@ +Device Configuration +========================= + +The devices receive their configuration from two sources: + +- The TangoDB database, for static *properties*, +- Externally, from the user, or a control system, that set *control attributes* (see the section for each device for what to set, and :ref:`attributes` for how to set them). + +.. _tangodb: + +TangoDB +------------------------- + +The TangoDB database is a persistent store for the properties of each device. The properties encode static settings, such as the hardware addresses, and default values for control attributes. + +Each device queries the TangoDB for the value of its properties during the ``initialise()`` call. Default values for control attributes can then be applied by explicitly calling ``set_defaults()``. The ``boot`` device also calls ``set_defaults()`` when initialising the station. The rationale being that the defaults can be applied at boot, but shouldn't be applied automatically during operations, as not to disturb running hardware. + +Device interaction +```````````````````````````` + +The properties of a device can be queried from the device directly:: + + # get a list of all the properties + property_names = device.get_property_list("*") + + # fetch the values of the given properties. returns a {property: value} dict. + property_dict = device.get_property(property_names) + +Properties can also be changed:: + + changeset = { "property": "new value" } + + device.put_property(changeset) + +Note that new values for properties will only be picked up by the device during ``initialise()``, so you will have to turn the device off and on. + +Command-line interaction +`````````````````````````` + +The content of the TangoDB can be dumped from the command line using:: + + bin/dump_ConfigDb.sh > tangodb-dump.json + +and changes can be applied using:: + + bin/update_ConfigDb.sh changeset.json + +.. note:: The ``dsconfig`` docker container needs to be running for these commands to work. + +Jive +`````````````````````````` + +The TangoDB can also be interactively queried and modified using Jive. Jive is an X11 application provided by the ``jive`` image as part of the software stack of the station. It must however be started on-demand, with a correctly configured ``$DISPLAY``:: + + cd docker-compose + make start jive + +If Jive does not appear, check ``docker logs jive`` to see what went wrong. + +For information on how to use Jive, see https://tango-controls.readthedocs.io/en/latest/tools-and-extensions/built-in/jive/. + +.. note:: If you need an X11 server on Windows, see :ref:`x11_on_windows`. + diff --git a/docs/source/devices/docker.rst b/docs/source/devices/docker.rst new file mode 100644 index 0000000000000000000000000000000000000000..6ac9d06d452b5c39a48d27687b225cd48c0af769 --- /dev/null +++ b/docs/source/devices/docker.rst @@ -0,0 +1,18 @@ +.. _docker: + +Docker +==================== + +The ``docker == DeviceProxy("STAT/Docker/1")`` device controls the docker containers. It allows starting and stopping them, and querying whether they are running. Each container is represented by two attributes: + +:<container>_R: Returns whether the container is running. + + :type: ``bool`` + +:<container>_RW: Set to ``True`` to start the container, and to ``False`` to stop it. + + :type: ``bool`` + +.. warning:: Do *not* stop the ``tango`` container, as doing so cripples the Tango infrastructure, leaving the station inoperable. It is also not wise to stop the ``device_docker`` container, as doing so would render this device unreachable. + + diff --git a/docs/source/devices/recv.rst b/docs/source/devices/recv.rst new file mode 100644 index 0000000000000000000000000000000000000000..847f8bb51f7f52850e66b0d25c01269b9961e726 --- /dev/null +++ b/docs/source/devices/recv.rst @@ -0,0 +1,15 @@ +RECV +==================== + +The ``recv == DeviceProxy("STAT/RECV/1")`` device controls the RCUs, the LBA antennas, and HBA tiles. Central to its operation are the masks (see also :ref:`attribute-masks`): + +:RCU_mask_RW: Controls which RCUs will actually be configured when attributes referring to RCUs are written. + + :type: ``bool[N_RCUs]`` + +:Ant_mask_RW: Controls which antennas will actually be configured when attributes referring to antennas are written. + + :type: ``bool[N_RCUs][N_antennas_per_RCU]`` + +Typically, ``N_RCUs == 32``, and ``N_antennas_per_RCU == 3``. + diff --git a/docs/source/devices/sdp.rst b/docs/source/devices/sdp.rst new file mode 100644 index 0000000000000000000000000000000000000000..c4d4032f47e7e4988437adb54dc7778bd53dc4b1 --- /dev/null +++ b/docs/source/devices/sdp.rst @@ -0,0 +1,95 @@ +SDP +==================== + +The ``sdp == DeviceProxy("STAT/SDP/1")``` device controls the digital signal processing in SDP, performed by the firmware on the FPGAs on the Uniboards. Central to its operation is the mask (see also :ref:`attribute-masks`): + +:TR_fpga_mask_RW: Controls which FPGAs will actually be configured when attributes referring to FPGAs are written. + + :type: ``bool[N_fpgas]`` + +Typically, ``N_fpgas == 16``. + +See the following links for a full description of the SDP monitoring and control points: + +- https://support.astron.nl/confluence/pages/viewpage.action?spaceKey=L2M&title=L2+STAT+Decision%3A+SC+-+SDP+OPC-UA+interface +- https://plm.astron.nl/polarion/#/project/LOFAR2System/wiki/L2%20Interface%20Control%20Documents/SC%20to%20SDP%20ICD + +Basic configuration +--------------------- + +The following points are significant for the operations of this device: + +:FPGA_processing_enable_R: Whether the FPGA is processing its input. + + :type: ``bool[N_fpgas]`` + +:TR_fpga_communication_error_R: Whether the FPGAs can be reached. + + :type: ``bool[N_fpgas]`` + +Data-quality information +--------------------------- + +The following fields describe the data quality: + +:FPGA_signal_input_mean_R: Mean value of the last second of input (in ADC quantisation units). Should be close to 0. + + :type: ``double[N_fpgas][N_ants_per_fpga]`` + +:FPGA_signal_input_rms_R: Root means square value of the last second of input (in ADC quantisation units). ``rms^2 = mean^2 + std^2``. Values above 2048 indicate strong RFI. + + :type: ``double[N_fpgas][N_ants_per_fpga]`` + +Version Information +--------------------- + +The following fields provide version information: + +:FPGA_firmware_version_R: The active firmware images. + + :type: ``str[N_fpgas]`` + +:FPGA_hardware_version_R: The versions of the boards hosting the FPGAs. + + :type: ``str[N_fpgas]`` + +:TR_software_version_R: The version of the server providing the OPC-UA interface. + + :type: ``str[N_fpgas]`` + +Waveform Generator +--------------------- + +The antenna input of SDP can be replaced by an internal waveform generator for debugging and testing purposes. The generator is configured per antenna per FPGA: + +:FPGA_wg_enable_RW: Whether the waveform generator is enabled for each input. + + :type: ``bool[N_fpgas][N_ants_per_fpga]`` + +:FPGA_wg_phase_RW: The phases of the generated waves (in degrees). The generator needs to be turned off and on if this is changed, in order to bring the generators in sync. + + :type: ``float32[N_fpgas][N_ants_per_fpga]`` + +:FPGA_wg_frequency_RW: The frequencies of the generated waves (in Hz). The frequency of a subband ``s`` is LBA: ``s * 200e6/1024``, HBA low band: ``(512 + s) * 200e6/1024``, HBA high band: ``(1024 + s) * 200e6/1024``. + + :type: ``float32[N_fpgas][N_ants_per_fpga]`` + +:FPGA_wg_amplitude_RW: The amplitudes of the generated waves. Useful is a value of ``0.1``, as higher risks clipping. + + :type: ``float32[N_fpgas][N_ants_per_fpga]`` + +Usage example +``````````````````````` + +For example, the following code inserts a wave on LBA subband 102 on FPGAs 8 - 11:: + + # configure FPGAs to control + sdp.TR_fpga_mask_RW = [False] * 8 + [True] * 4 + [False] * 4 + + # configure waveform generator + sdp.FPGA_wg_phase_RW = [[0] * 12] * 16 + sdp.FPGA_wg_amplitude_RW = [[0.1] * 12] * 16 + sdp.FPGA_wg_frequency_RW = [[102 * 200e6/1024] * 12] * 16 + + # enable waveform generator + sdp.FPGA_wg_enable_RW = [[True] * 12] * 16 diff --git a/docs/source/devices/sst-xst.rst b/docs/source/devices/sst-xst.rst new file mode 100644 index 0000000000000000000000000000000000000000..cdb689e457dc2d6abebcfc1391f057135f30b722 --- /dev/null +++ b/docs/source/devices/sst-xst.rst @@ -0,0 +1,109 @@ +SST and XST +==================== + +The ``sst == DeviceProxy("STAT/SST/1")`` and ``xst == DeviceProxy("STAT/XST/1")`` devices manages the SSTs (subband statistics) and XSTs (crosslet statistics), respectively. The statistics are emitted piece-wise through UDP packets by the FPGAs on the Uniboards in SDP. By default, each device configures the statistics to be streamed to itself (the device), from where the user can obtain them. + +The statistics are exposed in two ways, as: + +- *Attributes*, representing the most recently received values, +- *TCP stream*, to allow the capture and recording of the statistics over any period of time. + +See the following links for a full description of the SST and XST monitoring and control points: + +- https://support.astron.nl/confluence/pages/viewpage.action?spaceKey=L2M&title=L2+STAT+Decision%3A+SC+-+SDP+OPC-UA+interface +- https://plm.astron.nl/polarion/#/project/LOFAR2System/wiki/L2%20Interface%20Control%20Documents/SC%20to%20SDP%20ICD + +SST Statistics attributes +------------------------------ + +The SSTs represent the amplitude of the signal in each subband, for each antenna, as an integer value. They are exposed through the following attributes: + +:sst_R: Amplitude of each subband, from each antenna. + + :type: ``uint64[N_ant][N_subbands]`` + +:sst_timestamp_R: Timestamp of the data, per antenna. + + :type: ``uint64[N_ant]`` + +:integration_interval_R: Timespan over which the SSTs were integrated, per antenna. + + :type: ``float32[N_ant]`` + +:subbands_calibrated_R: Whether the subband data was calibrated using the subband weights. + + :type: ``bool[N_ant]`` + +Typically, ``N_ant == 192``, and ``N_subbands == 512``. + +XST Statistics attributes +------------------------------ + +The XSTs represent the cross-correlations between each pair of antennas, as complex values. The phases and amplitudes of the XSTs represent the phase and amplitude difference between the antennas, respectively. They are exposed as a matrix ``xst[a][b]``, of which only the triangle ``a<=b`` is filled, as the cross-correlation between antenna pairs ``(b,a)`` is equal to the complex conjugate of the cross-correlation of ``(a,b)``. The other triangle contains incidental values, but will be mostly 0. + +Complex values which cannot be represented in Tango attributes. Instead, the XST matrix is exposed as both their carthesian and polar parts: + +:xst_power_R, xst_phase_R: Amplitude and phase (in radians) of the crosslet statistics. + + :type: ``float32[N_ant][N_ant]`` + +:xst_real_R, xst_imag_R: Real and imaginary parts of the crosslet statistics. + + :type: ``float32[N_ant][N_ant]`` + +:xst_timestamp_R: Timestamp of each block. + + :type: ``int64[N_blocks]`` + +:integration_interval_R: Timespan over which the XSTs were integrated, for each block. + + :type: ``float32[N_blocks]`` + +Typically, ``N_ant == 192``, and ``N_blocks == 136``. + +The metadata refers to the *blocks*, which are emitted by the FPGAs to represent the XSTs between 12 x 12 consecutive antennas. The following code converts block numbers to the indices of the first antenna pair in a block:: + + from common.baselines import baseline_from_index + + def first_antenna_pair(block_nr: int) -> int: + coarse_a, coarse_b = baseline_from_index(block_nr) + return (coarse_a * 12, coarse_b * 12) + +Conversely, to calculate the block index for an antenna pair ``(a,b)``, use:: + + from common.baselines import baseline_index + + def block_nr(a: int, b: int) -> int: + return baseline_index(a // 12, b // 12) + +Subscribe to statistics streams +--------------------------------- + +The TCP stream interface allows a user to subscribe to the statistics packet streams, combined into a single TCP stream. The statistics will be streamed until the user disconnects, or the device is turned off. Any number of subscribers is supported, as bandwidth allows. Simply connect to the following port: + ++----------+----------------+ +| Device | TCP end point | ++==========+================+ +| SST | localhost:5101 | ++----------+----------------+ +| XST | localhost:5102 | ++----------+----------------+ + +The easiest way to capture this stream is to use our ``statistics_writer``, which will capture the statistics and store them in HDF5 file(s). The writer: + +- computes packet boundaries, +- processes the data of each packet, and stores their values into the matrix relevant for the mode, +- stores a matrix per timestamp, +- stores packet header information per timestamp, as HDF5 attributes, +- writes to a new file at a configurable interval. + +To run the writer:: + + cd devices/statistics_writer + python3 statistics_writer.py --mode SST --host localhost + +The correct port will automatically be chosen, depending on the given mode. See also ``statistics_writer.py -h`` for more information. + +The writer can also parse a statistics stream stored in a file. This allows the stream to be captured and processed independently. Capturing the stream can for example be done using ``netcat``:: + + nc localhost 5101 > SST-packets.bin diff --git a/docs/source/devices/using.rst b/docs/source/devices/using.rst new file mode 100644 index 0000000000000000000000000000000000000000..b5c41bd8089bc88d606ee0fe7bf8c442ff259475 --- /dev/null +++ b/docs/source/devices/using.rst @@ -0,0 +1,143 @@ +Using Devices +============= + +The station exposes *devices*, each of which is a remote software object that manages part of the station. Each device has the following properties: + +- It has a *state*, +- Many devices manage and represent hardware in the station, +- It exposes *read-only attributes*, that expose values from within the device or from the hardware it represents, +- It exposes *read-write attributes*, that allow controlling the functionality of the device, or the hardware it represents, +- It exposes *properties*, which are fixed configuration parameters (such as port numbers and timeouts), +- It exposes *commands*, that request the execution of a procedure in the device or in the hardware it manages. + +The devices are accessed remotely using ``DeviceProxy`` objects. See :doc:`../interfaces/control` on how to do this. + +States +------------ + +The state of a device is then queried with ``device.state()``. Each device can be in one of the following states: + +- ``DevState.OFF``: The device is not operating, +- ``DevState.INIT``: The device is being initialised, +- ``DevState.STANDBY``: The device is initialised and ready to be configured further, +- ``DevState.ON``: The device is operational. +- ``DevState.FAULT``: The device is malfunctioning. Functionality cannot be counted on. +- The ``device.state()`` function can throw an error, if the device cannot be reached at all. For example, because it's docker container is not running. See the :ref:`docker` device on how to start it. + +Each device provides the following commands to change the state: + +:off(): Turn the device ``OFF`` from any state. + +:initialise(): Initialise the device from the ``OFF`` state, to bring it to the ``STANDBY`` state. + +:on(): Mark the device as operational, from the ``STANDBY`` state, bringing it to ``ON``. + +The following procedure is a good way to bring a device to ``ON`` from any state:: + + def force_start(device): + if device.state() == DevState.FAULT: + device.off() + if device.state() == DevState.OFF: + device.initialise() + if device.state() == DevState.STANDBY: + device.on() + + return device.state() + +.. hint:: If a command gives you a timeout, the command will still be running until it finishes. You just won't know when it does or its result. In order to increase the timeout, use ``device.set_timeout_millis(timeout * 1000)``. + +FAULT +`````````` + +If a device enters the ``FAULT`` state, it means an error occurred that is fundamental to the operation of the software device. For example, the connection +to the hardware was lost. + +Interaction with the device in the ``FAULT`` state is undefined, and attributes cannot be read or written. The device needs to be reinitialised, which +typically involves the following sequence of commands:: + + # turn the device off completely first. + device.off() + + # setup any connections and threads + device.initialise() + + # turn on the device + device.on() + +Of course, the device could go into ``FAULT`` again, even during the ``initialise()`` command, for example because the hardware it manages is unreachable. To debug the fault condition, check the :doc:`../interfaces/logs` of the device in question. + +Initialise hardware +```````````````````` + +Most devices provide the following commands, in order to configure the hardware with base settings: + +:set_defaults(): Upload default attribute settings from the TangoDB to the hardware. + +:initialise_hardware(): For devices that control hardware, this command runs the hardware initialisation procedure. + +Typically, ``set_defaults()`` and ``initialise_hardware()`` are called in that order in the ``STANDBY`` state. See also :ref:`boot`, which provides functionality to initialise all the devices. + +.. _attributes: + +Attributes +------------ + +The device can be operated in ``ON`` state, where it exposes *attributes* and *commands*. The attributes can be accessed as python properties, for example:: + + recv = DeviceProxy("STAT/RECV/1") + + # turn on all LED0s + recv.RCU_LED0_RW = [True] * 32 + + # retrieve the status of all LED0s + print(recv.RCU_LED0_R) + +The attributes with an: + +- ``_R`` suffix are monitoring points, reflecting the state of the hardware, and are thus read-only. +- ``_RW`` suffix are control points, reflecting the desired state of the hardware. They are read-write, where writing requests the hardware to set the specified value. Reading them returns the last requested value. + +Meta data +````````````` + +A description of the attribute can be retrieved using:: + + print(recv.get_attribute_config("RCU_LED0_R").description) + +.. _attribute-masks: + +Attribute masks +--------------------- + +Several devices employ *attribute masks* in order to toggle which elements in their hardware array are actually to be controlled. This construct is necessary as most control points consist of arrays of values that cover all hardware elements. These array control points are always fully sent: it is not possible to update only a single element without uploading the rest. Without a mask, it is impossible to control a subset of the hardware. + +The masks only affect *writing* to attributes. Reading attributes (monitoring points) always result in data for all elements in the array. + +For example, the ``RCU_mask_RW`` array is the RCU mask in the ``recv`` device. It behaves as follows, when we interact with the ``RCU_LED0_R(W)`` attributes:: + + recv = DeviceProxy("STAT/RECV/1") + + # set mask to control all RCUs + recv.RCU_mask_RW = [True] * 32 + + # request to turn off LED0 for all RCUs + recv.RCU_LED0_RW = [False] * 32 + + # <--- all LED0s are now off + # recv.RCU_LED0_R should show this, + # if you have the RCU hardware installed. + + # set mask to only control RCU 3 + mask = [False] * 32 + mask[3] = True + recv.RCU_mask_RW = mask + + # request to turn on LED0, for all RCUs + # due to the mask, only LED0 on RCU 3 + # will be set. + recv.RCU_LED0_RW = [True] * 32 + + # <--- only LED0 on RCU3 is now on + # recv.RCU_LED0_R should show this, + # if you have the RCU hardware installed. + diff --git a/docs/source/faq.rst b/docs/source/faq.rst new file mode 100644 index 0000000000000000000000000000000000000000..1ed4fceacb85e6862c6f49d725fd4c53eb096081 --- /dev/null +++ b/docs/source/faq.rst @@ -0,0 +1,154 @@ +FAQ +=================================== + +Connecting to devices +-------------------------------------------------------------------------------------------------------------- + +My device is unreachable, but the device logs say it's running fine? +`````````````````````````````````````````````````````````````````````````````````````````````````````````````` + +The ``$HOSTNAME`` may have been incorrectly guessed by ``docker-compose/Makefile``, or you accidently set it to an incorrect value. If you have ``$HOSTNAME`` set in the shell running ``make``, try:: + + unset HOSTNAME + make build + make stop + make start + +If this does not work, you need to set ``$HOSTNAME`` to something that resolves to your machine, both for external parties and for docker containers. See :ref:`corba`. + +I get "API_CorbaException: TRANSIENT CORBA system exception: TRANSIENT_NoUsableProfile" when trying to connect to a device? +```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````` + +See the previous answer. + +Docker +-------------------------------------------------------------------------------------------------------------- + +How do I prevent my containers from starting when I boot my computer? +```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````` + +You have to explicitly stop a container to prevent it from restarting. Use:: + + cd docker-compose + make stop <container> + +or plain ``make stop`` to stop all of them. + +Windows +-------------------------------------------------------------------------------------------------------------- + +How do I develop from Windows? +`````````````````````````````````````````````````````````````````````````````````````````````````````````````` + +Our setup is Linux-based, so the easiest way to develop is by using WSL2, which lets you run a Linux distro under Windows. You'll need to: + +- Install WSL2. See f.e. https://www.omgubuntu.co.uk/how-to-install-wsl2-on-windows-10 +- Install `Docker Desktop <https://hub.docker.com/editions/community/docker-ce-desktop-windows/>`_ +- Enable the WSL2 backend in Docker Desktop +- We also recommend to install `Windows Terminal <https://www.microsoft.com/en-us/p/windows-terminal/9n0dx20hk701>`_ + +.. _x11_on_windows: + +How do I run X11 applications on Windows? +`````````````````````````````````````````````````````````````````````````````````````````````````````````````` +If you need an X11 server on Windows: + +- Install `VcXsrv <https://sourceforge.net/projects/vcxsrv/>`_ +- Disable access control during its startup, +- Use ``export DISPLAY=host.docker.internal:0`` in WSL. + +You should now be able to run X11 applications from WSL and Docker. Try running ``xterm`` or ``xeyes`` to test. + + +SSTs/XSTs +-------------------------------------------------------------------------------------------------------------- + +Some SSTs/XSTs packets do arrive, but not all, and/or the matrices remain zero? +`````````````````````````````````````````````````````````````````````````````````````````````````````````````` + +So ``sst.nof_packets_received`` / ``xst.nof_packets_received`` is increasing, telling you packets are arriving. But they're apparently dropped or contain zeroes. + +The ``sdp.set_defaults()`` command, followed by ``sst.set_defaults()`` / ``xst.set_defaults()``, should reset that device to its default settings, which should result in a working system again. If not, or if the default configuration is not correct, check the following settings: + +- ``sdp.TR_fpga_mask_RW[x] == True``, to make sure we're actually configuring the FPGAs, +- ``sdp.FPGA_wg_enable_RW[x] == False``, or the Waveform Generator might be replacing our the antenna data with zeroes, +- ``sdp.FPGA_processing_enabled_R[x] == True``, to verify that the FPGAs are processing, or the values and timestamps will be zero, +- For XSTs, ``xst.FPGA_xst_processing_enabled_R[x] == True``, to verify that the FPGAs are computing XSTs, or the values will be zero. + +Furthermore, the ``sst`` and ``xst`` devices expose several packet counters to indicate where incoming packets were dropped before or during processing: + +- ``nof_invalid_packets_R`` increases if packets arrive with an invalid header, or of the wrong statistic for this device, +- ``nof_packets_dropped_R`` increases if packets could not be processed because the processing queue is full, so the CPU cannot keep up with the flow, +- ``nof_payload_errors_R`` increases if the packet was marked by the FPGA to have an invalid payload, which causes the device to discard the packet, + +I am not receiving any XSTs and/or SSTs packets from SDP! +`````````````````````````````````````````````````````````````````````````````````````````````````````````````` + +Are you sure? If ``sst.nof_packets_received`` / ``xst.nof_packets_received`` is actually increasing, the packets are arriving, but are not parsable by the SST/XST device. If so, see the previous question. + +The ``sdp.set_defaults()`` command, followed by ``sst.set_defaults()`` / ``xst.set_defaults()``, should reset that device to its default settings, which should result in a working system again. If not, or if the default configuration is not correct, check the following settings: + +- ``sdp.TR_fpga_mask_RW[x] == True``, to make sure we're actually configuring the FPGAs, +- ``sdp.FPGA_communication_error_R[x] == False``, to verify the FPGAs can be reached by SDP, +- SSTs: + + - ``sst.FPGA_sst_offload_enable_RW[x] == True``, to verify that the FPGAs are actually emitting the SSTs, + - ``sst.FPGA_sst_offload_hdr_eth_destination_mac_R[x] == <MAC of your machine's mtu=9000 interface>``, or the FPGAs will not send it to your machine. Use f.e. ``ip addr`` on the host to find the MAC address of your interface, and verify that its MTU is 9000, + - ``sst.FPGA_sst_offload_hdr_ip_destination_address_R[x] == <IP of your machine's mtu=9000 interface>``, or the packets will be dropped by the network or the kernel of your machine, + - ``sst.FPGA_sst_offload_hdr_ip_destination_address_R[x] == 5001``, or the packets will not be sent to a port that the SST device listens on. + +- XSTs: + + - ``xst.FPGA_sst_offload_enable_RW[x] == True``, to verify that the FPGAs are actually emitting the SSTs, + - ``xst.FPGA_xst_offload_hdr_eth_destination_mac_R[x] == <MAC of your machine's mtu=9000 interface>``, or the FPGAs will not send it to your machine. Use f.e. ``ip addr`` on the host to find the MAC address of your interface, and verify that its MTU is 9000, + - ``xst.FPGA_xst_offload_hdr_ip_destination_address_R[x] == <IP of your machine's mtu=9000 interface>``, or the packets will be dropped by the network or the kernel of your machine, + - ``xst.FPGA_xst_offload_hdr_ip_destination_address_R[x] == 5002``, or the packets will not be sent to a port that the XST device listens on. + +If this fails, see the next question. + +I am still not receiving XSTs and/or SSTs, even though the settings appear correct! +`````````````````````````````````````````````````````````````````````````````````````````````````````````````` + +Let's see where the packets get stuck. Let us assume your MTU=9000 network interface is called ``em2`` (see ``ip addr`` to check): + +- Check whether the data arrives on ``em2``. Run ``tcpdump -i em2 udp -nn -vvv -c 10`` to capture the first 10 packets. Verify: + + - The destination MAC must match that of ``em2``, + - The destination IP must match that of ``em2``, + - The destination port is correct (5001 for SST, 5002 for XST), + - The source IP falls within the netmask of ``em2`` (unless ``net.ipv4.conf.em2.rp_filter=0`` is configured), + - TTL >= 2, + +- If you see no data at all, the network will have swallowed it. Try to use a direct network connection, or a hub (which broadcasts all packets, unlike a switch), to see what is being emitted by the FPGAs. +- Check whether the data reaches user space on the host: + + - Turn off the ``sst`` or ``xst`` device. This will not stop the FPGAs from sending. + - Run ``nc -u -l -p 5001 -vv`` (or port 5002 for XSTs). You should see raw packets being printed. + - If not, the Linux kernel is swallowing the packets, even before it can be sent to our docker container. + +- Check whether the data reaches kernel space in the container: + + - Enter the docker device by running ``docker exec -it device-sst bash``. + - Run ``sudo bash`` to become root, + - Run ``apt-get install -y tcpdump`` to install tcpdump, + - Check whether packets arrive using ``tcpdump -i eth0 udp -c 10 -nn``, + - If not, Linux is not routing the packets to the docker container. + +- Check whether the data reaches user space in the container: + + - Turn off the ``sst`` or ``xst`` device. This will not stop the FPGAs from sending. + - Enter the docker device by running ``docker exec -it device-sst bash``. + - Run ``sudo bash`` to become root, + - Run ``apt-get install -y netcat`` to install netcat, + - Check whether packets arrive using ``nc -u -l -p 5001 -vv`` (or port 5002 for XSTs), + - If not, Linux is not routing the packets to the docker container correctly. + +- If still on error was found, you've likely hit a bug in our software. + +Other containers +-------------------------------------------------------------------------------------------------------------- + +The ELK container won't start, saying "max virtual memory areas vm.max_map_count [65530] is too low"? +`````````````````````````````````````````````````````````````````````````````````````````````````````````````` + +The ELK stack needs the ``vm.max_map_count`` sysctl kernel parameter to be at least 262144 to run. See :ref:`elk-kernel-settings`. diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..b81b9c631131a08319d1ec2e07f868455a9a956a --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,38 @@ +.. LOFAR2.0 Station Control documentation master file, created by + sphinx-quickstart on Wed Oct 6 13:31:53 2021. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to LOFAR2.0 Station Control's documentation! +==================================================== + +LOFAR2.0 Station Control is a software stack aimed to monitor, control, and manage a LOFAR2.0 station. In order to do so, it whips up a series of Docker containers, and combines the power of `Tango Controls <https://www.tango-controls.org/>`_, `PyTango <https://pytango.readthedocs.io/en/stable/>`_, `Docker <https://www.docker.com/>`_, `Grafana <https://grafana.com/>`_, `ELK <https://www.elastic.co/what-is/elk-stack>`_, `Jupyter Notebook <https://jupyter.org/>`_, and many others to provide a rich and powerful experience in using the station. + +Full monitoring and control access to the LOFAR2.0 station hardware is provided, by marshalling their rich `OPC-UA <https://opcfoundation.org/about/opc-technologies/opc-ua/>`_ interfaces. Higher-level logic makes it possible to easily configure and obtain the LOFAR station data products (beamlets, XSTs, SSTs, BSTs) from your local machine using Python, or through one of our provided web interfaces. + +Even without having access to any LOFAR2.0 hardware, you can install the full stack on your laptop, and experiment with the software interfaces. + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + installation + interfaces/overview + devices/using + devices/boot + devices/docker + devices/recv + devices/sdp + devices/sst-xst + devices/configure + configure_station + developer + faq + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/source/installation.rst b/docs/source/installation.rst new file mode 100644 index 0000000000000000000000000000000000000000..58ccf2cd360261256774826a7904973ac6c44b70 --- /dev/null +++ b/docs/source/installation.rst @@ -0,0 +1,83 @@ +Installation +================== + +You will need the following dependencies installed: + +- docker +- docker-compose +- git +- make + +You start with checking out the source code, f.e. the master branch, as well as the git submodules we use:: + + git clone https://git.astron.nl/lofar2.0/tango.git + cd tango + git submodule init + git submodule update + +Next, we bootstrap the system. This will build our docker images, start key ones, and load the base configuration. This may take a while:: + + cd docker-compose + make bootstrap + +If you do have access to LOFAR station hardware, you must upload its configuration to the configuration database. See :doc:`configure_station`. + +Now we are ready to start the other containers:: + + make start + +and make sure they are all up and running:: + + make status + +You should see the following state: + +- Containers ``astor``, ``hdbpp-viewer``, ``jive``, ``log-viewer`` and ``pogo`` will have State ``Exit 1``. These are containers that are interactive X11 tools, and not needed for now, +- Other containers have either State ``Up`` or ``Exit 0``. + +If not, you can inspect why with ``docker logs <container>``. Note that the containers will automatically be restarted on failure, and also if you reboot. Stop them explicitly to bring them down (``make stop <container>``). + +Post-boot Initialisation +--------------------------- + +After bootstrapping, and after a reboot, the software and hardware of the station needs to be explicitly initialised. Note that the docker containers do restart automatically at system boot. + +The following commands start all the software devices to control the station hardware, and initialise the hardware with the configured default settings. Go to http://localhost:8888, start a new *Station Control* notebook, and initiate the software boot sequence:: + + # reset our boot device + boot.off() + assert boot.state() == DevState.OFF + boot.initialise() + assert boot.state() == DevState.STANDBY + boot.on() + assert boot.state() == DevState.ON + + # start and initialise the other devices + boot.initialise_station() + + # wait for the devices to be initialised + import time + + while boot.initialising_station_R: + print(f"Still initialising station. {boot.initialisation_progress_R}% complete. State: {boot.initialisation_status_R}") + time.sleep(1) + + # print conclusion + if boot.initialisation_progress_R == 100: + print("Done initialising station.") + else: + print(f"Failed to initialise station: {boot.initialisation_status_R}") + +See :ref:`boot` for more information on the ``boot`` device. + +.. _elk-kernel-settings: + +ELK +```` + +The ELK stack requires some kernel settings to be tuned, before it will start. Although ``make bootstrap`` configures the kernel, these settings will not stick after a reboot. You will need to run either:: + + make start elk-configure-host + make restart elk + +after reboot, or configure your system to set ``sysctl -w vm.max_map_count=262144`` (or higher) as root during boot. diff --git a/docs/source/interfaces/control.rst b/docs/source/interfaces/control.rst new file mode 100644 index 0000000000000000000000000000000000000000..adb5775527f554b0ca130afe5c478b165e64ab65 --- /dev/null +++ b/docs/source/interfaces/control.rst @@ -0,0 +1,84 @@ +Monitoring & Control +======================== + +The main API to control the station is through the `Tango Controls <https://tango-controls.readthedocs.io/en/latest/>`_ API we expose on port 10000, which is most easily accessed using a `PyTango <https://pytango.readthedocs.io/en/stable/client_api/index.html>`_ client. The Jupyter Notebook installation we provide is such a client. + +.. _jupyter: + +Jupyter Notebooks +------------------------ + +The station offers Juypyter notebooks On http://localhost:8888, which allow one to interact with the station, for example to set control points, access monitoring points, or to graph their values. + +The notebooks provide some predefined variables, so you don't have to look them up: + +.. literalinclude:: ../../../docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py + +Note: the Jupyter notebooks use enhancements from the ``itango`` suite, which provide tab completions, but also the ``Device`` alias for ``DeviceProxy`` as was used in the Python examples in the next section. + +For example, you can start a new *Station Control* notebook (File->New Notebook->StationControl), and access these devices: + +.. image:: jupyter_basic_example.png + +.. _pytango-section: + +PyTango +------------------------ + +To access a station from scratch using Python, we need to install some dependencies:: + + pip3 install tango + +Then, if we know what devices are available on the station, we can access them directly:: + + import tango + import os + + # Tango needs to know where our Tango API is running. + os.environ["TANGO_HOST"] = "localhost:10000" + + # Construct a remote reference to a specific device. + # One can also use "tango://localhost:10000/STAT/Boot/1" if TANGO_HOST is not set + boot_device = tango.DeviceProxy("STAT/Boot/1") + + # Print the device's state. + print(boot_device.state()) + +To obtain a list of all devices, we need to access the database:: + + import tango + + # Tango needs to know where our Tango API is running. + import os + os.environ["TANGO_HOST"] = "localhost:10000" + + # Connect to the database. + db = tango.Database() + + # Retrieve the available devices, excluding any Tango-internal ones. + # This returns for example: ['STAT/Boot/1', 'STAT/Docker/1', ...] + devices = list(db.get_device_exported("STAT/*")) + + # Connect to any of them. + any_device = tango.DeviceProxy(devices[0]) + + # Print the device's state. + print(any_device.state()) + +.. _rest-api: + +ReST API +------------------------ + +We also provide a ReST API to allow the station to be controlled without needing to use the Tango API. The root access point is http://localhost:8080/tango/rest/v10/hosts/databaseds;port=10000/ (credentials: tango-cs/tango). This API allows for: + +- getting and setting attribute values, +- calling commands, +- retrieving the device state, +- and more. + +For example, retrieving http://localhost:8080/tango/rest/v10/hosts/databaseds;port=10000/devices/STAT/SDP/1/state returns the following JSON document:: + + {"state":"ON","status":"The device is in ON state."} + +For a full description of this API, see https://tango-rest-api.readthedocs.io/en/latest/. diff --git a/docs/source/interfaces/elk_last_hour.png b/docs/source/interfaces/elk_last_hour.png new file mode 100644 index 0000000000000000000000000000000000000000..d6f2a73c9ba754a5a6d5aeece1382906040acb15 Binary files /dev/null and b/docs/source/interfaces/elk_last_hour.png differ diff --git a/docs/source/interfaces/elk_log_fields.png b/docs/source/interfaces/elk_log_fields.png new file mode 100644 index 0000000000000000000000000000000000000000..c5774931f23933be6033e396220b2459409b1def Binary files /dev/null and b/docs/source/interfaces/elk_log_fields.png differ diff --git a/docs/source/interfaces/grafana_dashboard_1.png b/docs/source/interfaces/grafana_dashboard_1.png new file mode 100644 index 0000000000000000000000000000000000000000..448a9bd993b264cf35e98229f12829256f775029 Binary files /dev/null and b/docs/source/interfaces/grafana_dashboard_1.png differ diff --git a/docs/source/interfaces/grafana_dashboard_2.png b/docs/source/interfaces/grafana_dashboard_2.png new file mode 100644 index 0000000000000000000000000000000000000000..d7c34991d97cd22a209d1f02502afa1f439acf4e Binary files /dev/null and b/docs/source/interfaces/grafana_dashboard_2.png differ diff --git a/docs/source/interfaces/jupyter_basic_example.png b/docs/source/interfaces/jupyter_basic_example.png new file mode 100644 index 0000000000000000000000000000000000000000..c7e35204cc72b63e8ea2d81c2bdad337d3ce72a1 Binary files /dev/null and b/docs/source/interfaces/jupyter_basic_example.png differ diff --git a/docs/source/interfaces/logs.rst b/docs/source/interfaces/logs.rst new file mode 100644 index 0000000000000000000000000000000000000000..fa0d29765c5d228454222a8f4a8d3d8f935c46be --- /dev/null +++ b/docs/source/interfaces/logs.rst @@ -0,0 +1,44 @@ +Logs +================== + +The devices, and the docker containers in general, produce logging output. The easiest way to access the logs of a specific container is to ask docker directly. For example, to access and follow the most recent logs of the ``device-sdp`` container, execute on the host:: + + docker logs -n 100 -f device-sdp + +This is mostly useful for interactive use. + +.. _elk: + +ELK +------------------ + +To monitor the logs remotely, or to browse older logs, use the *ELK stack* that is included on the station, and served on http://localhost:5601. ELK, or ElasticSearch + Logstash + Kibana, is a popular log collection and querying system. Currently, the following logs are collected in our ELK installation: + +- Logs of all devices, +- Logs of the Docker containers. + +If you browse to the ELK stack (actually, it is Kibana providing the GUI), your go-to is the *Discover* view at http://localhost:5601/app/discover. There, you can construct (and save, load) a dashboard that provides a custom view of the logs, based on the *index pattern* ``logstash-*``. There is a lot to take in, and there are excellent Kibana tutorials on the web. + +To get going, use for example `this dashboard <http://localhost:5601/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))&_a=(columns:!(extra.lofar_id,program,level,message),filters:!(),index:'1e8ca200-1be0-11ec-a85f-b97e4206c18b',interval:auto,query:(language:kuery,query:'extra.lofar_id.keyword%20:%20*'),sort:!())>`_, which shows the logs of the last hour, with some useful columns added to the default timestamp and message columns. Expand the time range if no logs appear, to look further back. You should see something like: + +.. image:: elk_last_hour.png + +ELK allows you to filter, edit the columns, and a lot more. We enrich the log entries with several extra fields, for example the device that generated it, and stack traces if available. Click on the ``>`` before a log entry and the information expands, showing for example: + +.. image:: elk_log_fields.png + +Furthermore, statistics from the ELK stack, such as the number of ERROR log messages, are made available as a data source in :doc:`monitoring`. + +LogViewer +------------------ + +For each device, Tango collects the logs as well. These can be viewed with the LogViewer X11 application. Make sure ``$DISPLAY`` is set, and run:: + + cd docker-compose + make start logviewer + +If LogViewer does not appear, check ``docker logs logviewer`` to see what went wrong. + +For information on how to use the LogViewer, see https://tango-controls.readthedocs.io/en/latest/tools-and-extensions/built-in/logviewer/logviewer.html. + +.. note:: If you need an X11 server on Windows, see :ref:`x11_on_windows`. diff --git a/docs/source/interfaces/monitoring.rst b/docs/source/interfaces/monitoring.rst new file mode 100644 index 0000000000000000000000000000000000000000..bb1ef494b320a40cd44aec789a0cf8d88653fa2a --- /dev/null +++ b/docs/source/interfaces/monitoring.rst @@ -0,0 +1,51 @@ +Monitoring GUIs +======================== + +Each device exposes a list of monitoring points as attributes with the ``_R`` prefix. These can be accessed interactively from a controle console (such as Jupyter), but that will not scale. + +Grafana +------------------------ + +We offer `Grafana <https://grafana.com/>`_ dashboards on http://localhost:3000 that provide a quick overview of the station's status, including temperatures and settings. Several dashboards are included. An example: + +.. image:: grafana_dashboard_1.png +.. image:: grafana_dashboard_2.png + +NOTE: These dashboards are highly subject to change. The above examples provide an impression of a possible overview of the station state. + +You are encouraged to inspect each panel (graph) to see the underlying database query and settings. Use the small arrow in the panel's title to get a drop-down menu of options, and select *inspect*. See the Grafana documentation for further information. + +The Grafana dashboards are configured with the following data sources: + +- :ref:`prometheus-section`, the time-series database that caches the latest values of all monitoring points (see next section), +- *Archiver DB*, the database that provides a long-term cache of attributes, +- :ref:`tangodb`, providing access to device properties (fixed settings), +- :ref:`elk`, the log output of the devices. + +.. _prometheus-section: + +Prometheus +------------------------- + +`Prometheus <https://prometheus.io/docs/introduction/overview/>`_ is a low-level monitoring system that allows us to periodically retrieve the values of all the attributes of all our devices, and cache them to be used in Grafana: + +- Every several seconds, Prometheus scrapes our `TANGO-Grafana Exporter <https://git.astron.nl/lofar2.0/ska-tango-grafana-exporter>`_ (our fork of https://gitlab.com/ska-telescope/TANGO-grafana.git), collecting all values of all the device attributes (except the large ones, for performance reasons). +- Prometheus can be queried directly on http://localhost:9090, +- The TANGO-Grafana Exporter can be queried directly on http://localhost:8000, +- The query language is `PromQL <https://prometheus.io/docs/prometheus/latest/querying/basics/>`_, which is also used in Grafana to query Prometheus, + +Prometheus stores attributes in the following format:: + + device_attribute{device="stat/recv/1", + dim_x="32", dim_y="0", + instance="tango-prometheus-exporter:8000", + job="tango", + label="RCU_temperature_R", + name="RCU_temperature_R", + type="float", + x="00", y="0"} + +The above describes a single data point and its labels. The primary identifying labels are ``device`` and ``name``. Each point furthermore has a value (integer) and a timestamp. The following transformations take place: + +- For 1D and 2D attributes, each array element is its own monitoring point, with ``x`` and ``y`` labels describing the indices. The labels ``dim_x`` and ``dim_y`` describe the array dimensionality, +- Attributes with string values get a ``str_value`` label describing their value. diff --git a/docs/source/interfaces/overview.rst b/docs/source/interfaces/overview.rst new file mode 100644 index 0000000000000000000000000000000000000000..a00ab5710ad863b4f10d1bb0ee93ab3f547826d5 --- /dev/null +++ b/docs/source/interfaces/overview.rst @@ -0,0 +1,41 @@ +Interfaces +====================== + +The station provides the following interfaces accessible through your browser (assuming you run on `localhost`): + ++---------------------+---------+----------------------+-------------------+ +|Interface |Subsystem|URL |Default credentials| ++=====================+=========+======================+===================+ +| :ref:`jupyter` |Jupyter |http://localhost:8888 | | ++---------------------+---------+----------------------+-------------------+ +| :doc:`monitoring` |Grafana |http://localhost:3000 |admin/admin | ++---------------------+---------+----------------------+-------------------+ +| :doc:`logs` |Kibana |http://localhost:5601 | | ++---------------------+---------+----------------------+-------------------+ + +Futhermore, there are some low-level interfaces: + ++---------------------------+------------------+-----------------------+-------------------+ +|Interface |Subsystem |URL |Default credentials| ++===========================+==================+=======================+===================+ +| :ref:`pytango-section` |Tango |tango://localhost:10000| | ++---------------------------+------------------+-----------------------+-------------------+ +| :ref:`prometheus-section` |Prometheus |http://localhost:9090 | | ++---------------------------+------------------+-----------------------+-------------------+ +| TANGO-Grafana Exporter |Python HTTPServer |http://localhost:8000 | | ++---------------------------+------------------+-----------------------+-------------------+ +| :ref:`rest-api` |tango-rest |http://localhost:8080 |tango-cs/tango | ++---------------------------+------------------+-----------------------+-------------------+ +| :ref:`tangodb` |MariaDB |http://localhost:3306 |tango/tango | ++---------------------------+------------------+-----------------------+-------------------+ +|Archive Database |MariaDB |http://localhost:3307 |tango/tango | ++---------------------------+------------------+-----------------------+-------------------+ +|Log Database |ElasticSearch |http://localhost:9200 | | ++---------------------------+------------------+-----------------------+-------------------+ + +.. toctree:: + :hidden: + + control + monitoring + logs diff --git a/jupyter-notebooks/.ipynb_checkpoints/test_device-checkpoint.ipynb b/jupyter-notebooks/.ipynb_checkpoints/test_device-checkpoint.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..16749ce8b9e2f50e6cae56615790b9e5f19211ef --- /dev/null +++ b/jupyter-notebooks/.ipynb_checkpoints/test_device-checkpoint.ipynb @@ -0,0 +1,163 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 3, + "id": "waiting-chance", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "import numpy" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "moving-alexandria", + "metadata": {}, + "outputs": [ + { + "ename": "DevFailed", + "evalue": "DevFailed[\nDevError[\n desc = device lts/xst/1 not defined in the database !\n origin = DataBase::ImportDevice()\n reason = DB_DeviceNotDefined\nseverity = ERR]\n\nDevError[\n desc = Failed to execute command_inout on device sys/database/2, command DbImportDevice\n origin = Connection::command_inout()\n reason = API_CommandFailed\nseverity = ERR]\n\nDevError[\n desc = Can't connect to device lts/xst/1\n origin = DeviceProxy::DeviceProxy\n reason = API_DeviceNotDefined\nseverity = ERR]\n]", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mDevFailed\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/tmp/ipykernel_22/3496973635.py\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0md\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mDeviceProxy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"LTS/XST/1\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__DeviceProxy__init__\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 205\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_executors\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mGreenMode\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mGevent\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpop\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'threadpool'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 206\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_executors\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mGreenMode\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mAsyncio\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpop\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'asyncio_executor'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 207\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mDeviceProxy\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__init_orig__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 208\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 209\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mDevFailed\u001b[0m: DevFailed[\nDevError[\n desc = device lts/xst/1 not defined in the database !\n origin = DataBase::ImportDevice()\n reason = DB_DeviceNotDefined\nseverity = ERR]\n\nDevError[\n desc = Failed to execute command_inout on device sys/database/2, command DbImportDevice\n origin = Connection::command_inout()\n reason = API_CommandFailed\nseverity = ERR]\n\nDevError[\n desc = Can't connect to device lts/xst/1\n origin = DeviceProxy::DeviceProxy\n reason = API_DeviceNotDefined\nseverity = ERR]\n]" + ] + } + ], + "source": [ + "d=DeviceProxy(\"LTS/XST/1\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ranking-aluminum", + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'd' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/tmp/ipykernel_22/3213548382.py\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mstate\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mstate\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"OFF\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minitialise\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mtime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mNameError\u001b[0m: name 'd' is not defined" + ] + } + ], + "source": [ + "state = str(d.state())\n", + "\n", + "if state == \"OFF\":\n", + " d.initialise()\n", + " time.sleep(1)\n", + "state = str(d.state())\n", + "if state == \"STANDBY\":\n", + " d.on()\n", + "state = str(d.state())\n", + "if state == \"ON\":\n", + " print(\"Device is now in on state\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "beneficial-evidence", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "packet_count_R [55]\n", + "last_packet_timestamp_R [1623249385]\n", + "queue_percentage_used_R [0.]\n", + "State <function __get_command_func.<locals>.f at 0x7fcb205fd0d0>\n", + "Status <function __get_command_func.<locals>.f at 0x7fcb205fd0d0>\n" + ] + } + ], + "source": [ + "attr_names = d.get_attribute_list()\n", + "\n", + "for i in attr_names:\n", + " exec(\"value = print(i, d.{})\".format(i))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "sporting-current", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3.0" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "d.RCU_mask_RW = [False, False, False, False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False,]\n", + "time.sleep(1)\n", + "print(d.RCU_mask_RW)\n", + "\n", + "monitor_rate = d.RCU_monitor_rate_RW\n", + "print(\"current monitoring rate: {}, setting to {}\".format(monitor_rate, monitor_rate + 1))\n", + "monitor_rate = monitor_rate + 1\n", + "\n", + "time.sleep(1)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "sharing-mechanics", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ruled-tracy", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "StationControl", + "language": "python", + "name": "stationcontrol" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/jupyter-notebooks/Archiving_load_test.ipynb b/jupyter-notebooks/Archiving_load_test.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..64430c99049390ca89b18996ad518c5f0e59de37 --- /dev/null +++ b/jupyter-notebooks/Archiving_load_test.ipynb @@ -0,0 +1,1265 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "d9f35471", + "metadata": {}, + "outputs": [], + "source": [ + "import sys, time\n", + "import numpy as np\n", + "sys.path.append('/hosthome/tango/devices')\n", + "from toolkit.archiver import *\n", + "from matplotlib import pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "5817986f", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Attribute lts/recv/1/version_r not found in archiving list!\n", + "Attribute lts/recv/1/opcua_missing_attributes_r not found in archiving list!\n", + "Attribute lts/recv/1/ant_mask_rw removed!\n", + "Attribute lts/recv/1/ant_status_r not found in archiving list!\n", + "Attribute lts/recv/1/clk_enable_pwr_r removed!\n", + "Attribute lts/recv/1/clk_i2c_status_r removed!\n", + "Attribute lts/recv/1/clk_pll_error_r removed!\n", + "Attribute lts/recv/1/clk_pll_locked_r removed!\n", + "Attribute lts/recv/1/clk_monitor_rate_rw removed!\n", + "Attribute lts/recv/1/clk_translator_busy_r removed!\n", + "Attribute lts/recv/1/hba_element_beamformer_delays_r removed!\n", + "Attribute lts/recv/1/hba_element_beamformer_delays_rw removed!\n", + "Attribute lts/recv/1/hba_element_led_r removed!\n", + "Attribute lts/recv/1/hba_element_led_rw removed!\n", + "Attribute lts/recv/1/hba_element_lna_pwr_r removed!\n", + "Attribute lts/recv/1/hba_element_lna_pwr_rw removed!\n", + "Attribute lts/recv/1/hba_element_pwr_r removed!\n", + "Attribute lts/recv/1/hba_element_pwr_rw removed!\n", + "Attribute lts/recv/1/rcu_adc_lock_r removed!\n", + "Attribute lts/recv/1/rcu_attenuator_r removed!\n", + "Attribute lts/recv/1/rcu_attenuator_rw removed!\n", + "Attribute lts/recv/1/rcu_band_r removed!\n", + "Attribute lts/recv/1/rcu_band_rw removed!\n", + "Attribute lts/recv/1/rcu_i2c_status_r removed!\n", + "Attribute lts/recv/1/rcu_id_r removed!\n", + "Attribute lts/recv/1/rcu_led0_r removed!\n", + "Attribute lts/recv/1/rcu_led0_rw removed!\n", + "Attribute lts/recv/1/rcu_led1_r removed!\n", + "Attribute lts/recv/1/rcu_led1_rw removed!\n", + "Attribute lts/recv/1/rcu_mask_rw removed!\n", + "Attribute lts/recv/1/rcu_monitor_rate_rw removed!\n", + "Attribute lts/recv/1/rcu_pwr_dig_r removed!\n", + "Attribute lts/recv/1/rcu_translator_busy_r removed!\n", + "Attribute lts/recv/1/rcu_version_r removed!\n", + "Attribute lts/recv/1/state removed!\n", + "Attribute lts/recv/1/status removed!\n", + "Attribute lts/recv/1/rcu_temperature_r already in archiving list!\n", + "Attribute lts/sdp/1/version_r not found in archiving list!\n", + "Attribute lts/sdp/1/opcua_missing_attributes_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_wg_amplitude_rw not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_wg_frequency_rw not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_wg_phase_rw not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_enable_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_enable_rw not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_hdr_eth_destination_mac_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_hdr_eth_destination_mac_rw not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_hdr_ip_destination_address_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_hdr_ip_destination_address_rw not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_hdr_udp_destination_port_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_hdr_udp_destination_port_rw not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_scale_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_scale_rw not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_firmware_version_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_global_node_index_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_hardware_version_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_processing_enable_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_processing_enable_rw not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_scrap_r removed!\n", + "Attribute lts/sdp/1/fpga_scrap_rw removed!\n", + "Attribute lts/sdp/1/fpga_signal_input_mean_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_signal_input_rms_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_sdp_info_antenna_band_index_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_sdp_info_block_period_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_sdp_info_f_adc_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_sdp_info_fsub_type_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_sdp_info_nyquist_sampling_zone_index_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_sdp_info_nyquist_sampling_zone_index_rw not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_sdp_info_observation_id_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_sdp_info_observation_id_rw not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_sdp_info_station_id_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_sdp_info_station_id_rw not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_subband_weights_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_subband_weights_rw not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_temp_r removed!\n", + "Attribute lts/sdp/1/fpga_weights_r removed!\n", + "Attribute lts/sdp/1/fpga_weights_rw removed!\n", + "Attribute lts/sdp/1/fpga_wg_amplitude_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_wg_enable_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_wg_enable_rw not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_wg_frequency_r not found in archiving list!\n", + "Attribute lts/sdp/1/fpga_wg_phase_r not found in archiving list!\n", + "Attribute lts/sdp/1/tr_fpga_mask_r not found in archiving list!\n", + "Attribute lts/sdp/1/tr_fpga_mask_rw not found in archiving list!\n", + "Attribute lts/sdp/1/tr_fpga_communication_error_r not found in archiving list!\n", + "Attribute lts/sdp/1/tr_sdp_config_first_fpga_nr_r not found in archiving list!\n", + "Attribute lts/sdp/1/tr_sdp_config_nof_beamsets_r not found in archiving list!\n", + "Attribute lts/sdp/1/tr_sdp_config_nof_fpgas_r not found in archiving list!\n", + "Attribute lts/sdp/1/tr_software_version_r not found in archiving list!\n", + "Attribute lts/sdp/1/tr_start_time_r not found in archiving list!\n", + "Attribute lts/sdp/1/tr_tod_r removed!\n", + "Attribute lts/sdp/1/tr_tod_pps_delta_r not found in archiving list!\n", + "Attribute lts/sdp/1/state not found in archiving list!\n", + "Attribute lts/sdp/1/status not found in archiving list!\n", + "Device LTS/SST/1 offline\n", + "Device LTS/XST/1 offline\n", + "Device LTS/UNB2/1 offline\n" + ] + } + ], + "source": [ + "# Apply the chosen JSON configuration file in directory toolkit/archiver_config/\n", + "archiver = Archiver(selector_filename='lofar2.json')" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "848dc5e7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lofar2.json\n" + ] + }, + { + "data": { + "text/plain": [ + "{'global_variables': {'development_polling_time': '10000',\n", + " 'development_archive_time': '60000'},\n", + " 'devices': {'LTS/RECV/1': {'environment': 'development',\n", + " 'include': ['rcu_temperature_r'],\n", + " 'exclude': ['CLK_Enable_PWR_R',\n", + " 'CLK_I2C_STATUS_R',\n", + " 'CLK_PLL_error_R',\n", + " 'CLK_PLL_locked_R',\n", + " 'CLK_translator_busy_R']},\n", + " 'LTS/SDP/1': {'environment': 'development', 'include': [], 'exclude': []},\n", + " 'LTS/SST/1': {'environment': 'development', 'include': [], 'exclude': []},\n", + " 'LTS/XST/1': {'environment': 'development', 'include': [], 'exclude': []},\n", + " 'LTS/UNB2/1': {'environment': 'development', 'include': [], 'exclude': []}}}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Print the configuration file (as a dictionary)\n", + "selector = archiver.selector\n", + "print(selector.filename)\n", + "env_dict = selector.get_dict()\n", + "env_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a81e8b3b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OFF\n" + ] + } + ], + "source": [ + "device_name = 'STAT/RECV/1'\n", + "d=DeviceProxy(device_name) \n", + "state = str(d.state())\n", + "print(state)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "f5394d09", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Attribute lts/recv/1/version_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/recv/1/opcua_missing_attributes_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/recv/1/ant_status_r will not be archived because polling is set to FALSE!\n" + ] + } + ], + "source": [ + "# Add RECV attributes to perform load test\n", + "archiver.add_attributes_by_device(device_name,global_archive_period=5000)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "ba3a25ac", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'tango://databaseds:10000/lts/recv/1/rcu_temperature_r': 'Read value for attribute RCU_temperature_R has not been updated'}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Print the errors for each attribute\n", + "# If the device is in OFF state, all its attributes should be in error (normal behaviour)\n", + "err_dict = archiver.get_subscriber_errors()\n", + "err_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "b4de92a0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Device is now in ON state\n" + ] + } + ], + "source": [ + "# Start the device\n", + "if state == \"OFF\":\n", + " time.sleep(1)\n", + " d.initialise()\n", + " time.sleep(1)\n", + "state = str(d.state())\n", + "if state == \"STANDBY\":\n", + " d.on()\n", + "state = str(d.state())\n", + "if state == \"ON\":\n", + " d.set_defaults()\n", + " print(\"Device is now in ON state\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "5d40b87c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('tango://databaseds:10000/lts/recv/1/rcu_temperature_r',\n", + " 'tango://databaseds:10000/lts/recv/1/ant_mask_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_enable_pwr_r',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_i2c_status_r',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_pll_error_r',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_pll_locked_r',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_monitor_rate_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_translator_busy_r',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_beamformer_delays_r',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_beamformer_delays_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_led_r',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_led_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_lna_pwr_r',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_lna_pwr_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_pwr_r',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_pwr_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_adc_lock_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_attenuator_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_attenuator_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_band_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_band_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_i2c_status_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_id_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_led0_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_led0_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_led1_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_led1_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_mask_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_monitor_rate_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_pwr_dig_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_translator_busy_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_version_r',\n", + " 'tango://databaseds:10000/lts/recv/1/state',\n", + " 'tango://databaseds:10000/lts/recv/1/status')" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Print the attributes currently managed by the event subscriber\n", + "attrs = archiver.get_subscriber_attributes()\n", + "attrs" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "678879e3", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'tango://databaseds:10000/lts/recv/1/rcu_temperature_r': 'Read value for attribute RCU_temperature_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/ant_mask_rw': 'Read value for attribute Ant_mask_RW has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_enable_pwr_r': 'Read value for attribute CLK_Enable_PWR_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_i2c_status_r': 'Read value for attribute CLK_I2C_STATUS_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_pll_error_r': 'Read value for attribute CLK_PLL_error_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_pll_locked_r': 'Read value for attribute CLK_PLL_locked_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_monitor_rate_rw': 'Read value for attribute CLK_monitor_rate_RW has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_translator_busy_r': 'Read value for attribute CLK_translator_busy_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_beamformer_delays_r': 'Read value for attribute HBA_element_beamformer_delays_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_beamformer_delays_rw': 'Read value for attribute HBA_element_beamformer_delays_RW has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_led_r': 'Read value for attribute HBA_element_led_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_led_rw': 'Read value for attribute HBA_element_led_RW has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_lna_pwr_r': 'Read value for attribute HBA_element_LNA_pwr_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_lna_pwr_rw': 'Read value for attribute HBA_element_LNA_pwr_RW has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_pwr_r': 'Read value for attribute HBA_element_pwr_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_pwr_rw': 'Read value for attribute HBA_element_pwr_RW has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_adc_lock_r': 'Read value for attribute RCU_ADC_lock_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_attenuator_r': 'Read value for attribute RCU_attenuator_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_attenuator_rw': 'Read value for attribute RCU_attenuator_RW has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_band_r': 'Read value for attribute RCU_band_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_band_rw': 'Read value for attribute RCU_band_RW has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_i2c_status_r': 'Read value for attribute RCU_I2C_STATUS_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_id_r': 'Read value for attribute RCU_ID_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_led0_r': 'Read value for attribute RCU_LED0_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_led0_rw': 'Read value for attribute RCU_LED0_RW has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_led1_r': 'Read value for attribute RCU_LED1_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_led1_rw': 'Read value for attribute RCU_LED1_RW has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_mask_rw': 'Read value for attribute RCU_mask_RW has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_monitor_rate_rw': 'Read value for attribute RCU_monitor_rate_RW has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_pwr_dig_r': 'Read value for attribute RCU_Pwr_dig_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_translator_busy_r': 'Read value for attribute RCU_translator_busy_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_version_r': 'Read value for attribute RCU_version_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/status': 'Storing Error: mysql_stmt_bind_param() failed, err=Buffer type is not supported'}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Print the errors for each attribute\n", + "err_dict = archiver.get_subscriber_errors()\n", + "err_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "d3904658", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Attribute Poll Period Archive Period \n", + "----------\n", + "LTS/RECV/1/rcu_temperature_r 10000 60000 \n", + "LTS/RECV/1/ant_mask_rw 1000 5000 \n", + "LTS/RECV/1/clk_enable_pwr_r 1000 5000 \n", + "LTS/RECV/1/clk_i2c_status_r 1000 5000 \n", + "LTS/RECV/1/clk_pll_error_r 1000 5000 \n", + "LTS/RECV/1/clk_pll_locked_r 1000 5000 \n", + "LTS/RECV/1/clk_monitor_rate_rw 1000 5000 \n", + "LTS/RECV/1/clk_translator_busy_r 1000 5000 \n", + "LTS/RECV/1/hba_element_beamformer_delays_r 1000 5000 \n", + "LTS/RECV/1/hba_element_beamformer_delays_rw 1000 5000 \n", + "LTS/RECV/1/hba_element_led_r 1000 5000 \n", + "LTS/RECV/1/hba_element_led_rw 1000 5000 \n", + "LTS/RECV/1/hba_element_lna_pwr_r 1000 5000 \n", + "LTS/RECV/1/hba_element_lna_pwr_rw 1000 5000 \n", + "LTS/RECV/1/hba_element_pwr_r 1000 5000 \n", + "LTS/RECV/1/hba_element_pwr_rw 1000 5000 \n", + "LTS/RECV/1/rcu_adc_lock_r 1000 5000 \n", + "LTS/RECV/1/rcu_attenuator_r 1000 5000 \n", + "LTS/RECV/1/rcu_attenuator_rw 1000 5000 \n", + "LTS/RECV/1/rcu_band_r 1000 5000 \n", + "LTS/RECV/1/rcu_band_rw 1000 5000 \n", + "LTS/RECV/1/rcu_i2c_status_r 1000 5000 \n", + "LTS/RECV/1/rcu_id_r 1000 5000 \n", + "LTS/RECV/1/rcu_led0_r 1000 5000 \n", + "LTS/RECV/1/rcu_led0_rw 1000 5000 \n", + "LTS/RECV/1/rcu_led1_r 1000 5000 \n", + "LTS/RECV/1/rcu_led1_rw 1000 5000 \n", + "LTS/RECV/1/rcu_mask_rw 1000 5000 \n", + "LTS/RECV/1/rcu_monitor_rate_rw 1000 5000 \n", + "LTS/RECV/1/rcu_pwr_dig_r 1000 5000 \n", + "LTS/RECV/1/rcu_translator_busy_r 1000 5000 \n", + "LTS/RECV/1/rcu_version_r 1000 5000 \n", + "LTS/RECV/1/state 1000 5000 \n", + "LTS/RECV/1/status 1000 5000 \n" + ] + } + ], + "source": [ + "# Print the attribute periods\n", + "def print_periods(attrs):\n", + " print(\"{:<45} {:<15} {:<15}\".format('Attribute','Poll Period','Archive Period'))\n", + " print(\"----------\")\n", + " for a in attrs:\n", + " ap = AttributeProxy(a)\n", + " att_fqname = ap.get_device_proxy().name()+'/'+ap.name()\n", + " print(\"{:<45} {:<15} {:<15}\".format(att_fqname,ap.get_poll_period(),ap.get_property('archive_period')['archive_period'][0],sep='\\t'))\n", + "\n", + "attrs = archiver.get_subscriber_attributes()\n", + "print_periods(attrs)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "27ef5564", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'133.0 events/period'" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Print the current event subscriber archive frequency (number of total archive events per minute)\n", + "# Be aware that these statistics need some time (even minutes) since the device initialization to be reliable\n", + "archiver.get_subscriber_load()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "241b5282", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Attribute Record Freq Failure Freq \n", + "----------\n", + "LTS/RECV/1/rcu_temperature_r 0.0 1.0 \n", + "LTS/RECV/1/ant_mask_rw 10.0 3.0 \n", + "LTS/RECV/1/clk_enable_pwr_r 0.0 12.0 \n", + "LTS/RECV/1/clk_i2c_status_r 0.0 12.0 \n", + "LTS/RECV/1/clk_pll_error_r 0.0 12.0 \n", + "LTS/RECV/1/clk_pll_locked_r 0.0 12.0 \n", + "LTS/RECV/1/clk_monitor_rate_rw 10.0 3.0 \n", + "LTS/RECV/1/clk_translator_busy_r 0.0 12.0 \n", + "LTS/RECV/1/hba_element_beamformer_delays_r 0.0 12.0 \n", + "LTS/RECV/1/hba_element_beamformer_delays_rw 10.0 3.0 \n", + "LTS/RECV/1/hba_element_led_r 0.0 12.0 \n", + "LTS/RECV/1/hba_element_led_rw 10.0 3.0 \n", + "LTS/RECV/1/hba_element_lna_pwr_r 0.0 12.0 \n", + "LTS/RECV/1/hba_element_lna_pwr_rw 10.0 3.0 \n", + "LTS/RECV/1/hba_element_pwr_r 0.0 12.0 \n", + "LTS/RECV/1/hba_element_pwr_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_adc_lock_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_attenuator_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_attenuator_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_band_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_band_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_i2c_status_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_id_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_led0_r 10.0 3.0 \n", + "LTS/RECV/1/rcu_led0_rw 0.0 12.0 \n", + "LTS/RECV/1/rcu_led1_r 10.0 3.0 \n", + "LTS/RECV/1/rcu_led1_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_mask_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_monitor_rate_rw 0.0 12.0 \n", + "LTS/RECV/1/rcu_pwr_dig_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_translator_busy_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_version_r 13.0 0.0 \n", + "LTS/RECV/1/state 0.0 13.0 \n", + "LTS/RECV/1/status 0.0 6.0 \n" + ] + } + ], + "source": [ + "# Print the current attribute archive frequency (number of events per minute)\n", + "# E.G. if an attribute is supposed to be archived every 10s, its frequency value should be 6\n", + "def print_freq(attrs):\n", + " print(\"{:<45} {:<15} {:<15}\".format('Attribute','Record Freq','Failure Freq'))\n", + " print(\"----------\")\n", + " for a in attrs:\n", + " ap = AttributeProxy(a)\n", + " att_fqname = ap.get_device_proxy().name()+'/'+ap.name()\n", + " print(\"{:<45} {:<15} {:<15}\".format(att_fqname,archiver.get_attribute_freq(att_fqname),archiver.get_attribute_failures(att_fqname)))\n", + "\n", + "attrs = archiver.get_subscriber_attributes()\n", + "print_freq(attrs)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "25446390", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Attribute LTS/RECV/1/rcu_id_r removed!\n" + ] + } + ], + "source": [ + "# Update the archive time of an attribute\n", + "archiver.update_archiving_attribute('STAT/RECV/1/rcu_id_r',polling_period=1000,event_period=10000)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "9cc4f883", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Attribute Poll Period Archive Period \n", + "----------\n", + "LTS/RECV/1/rcu_temperature_r 10000 60000 \n", + "LTS/RECV/1/ant_mask_rw 1000 5000 \n", + "LTS/RECV/1/clk_enable_pwr_r 1000 5000 \n", + "LTS/RECV/1/clk_i2c_status_r 1000 5000 \n", + "LTS/RECV/1/clk_pll_error_r 1000 5000 \n", + "LTS/RECV/1/clk_pll_locked_r 1000 5000 \n", + "LTS/RECV/1/clk_monitor_rate_rw 1000 5000 \n", + "LTS/RECV/1/clk_translator_busy_r 1000 5000 \n", + "LTS/RECV/1/hba_element_beamformer_delays_r 1000 5000 \n", + "LTS/RECV/1/hba_element_beamformer_delays_rw 1000 5000 \n", + "LTS/RECV/1/hba_element_led_r 1000 5000 \n", + "LTS/RECV/1/hba_element_led_rw 1000 5000 \n", + "LTS/RECV/1/hba_element_lna_pwr_r 1000 5000 \n", + "LTS/RECV/1/hba_element_lna_pwr_rw 1000 5000 \n", + "LTS/RECV/1/hba_element_pwr_r 1000 5000 \n", + "LTS/RECV/1/hba_element_pwr_rw 1000 5000 \n", + "LTS/RECV/1/rcu_adc_lock_r 1000 5000 \n", + "LTS/RECV/1/rcu_attenuator_r 1000 5000 \n", + "LTS/RECV/1/rcu_attenuator_rw 1000 5000 \n", + "LTS/RECV/1/rcu_band_r 1000 5000 \n", + "LTS/RECV/1/rcu_band_rw 1000 5000 \n", + "LTS/RECV/1/rcu_i2c_status_r 1000 5000 \n", + "LTS/RECV/1/rcu_led0_r 1000 5000 \n", + "LTS/RECV/1/rcu_led0_rw 1000 5000 \n", + "LTS/RECV/1/rcu_led1_r 1000 5000 \n", + "LTS/RECV/1/rcu_led1_rw 1000 5000 \n", + "LTS/RECV/1/rcu_mask_rw 1000 5000 \n", + "LTS/RECV/1/rcu_monitor_rate_rw 1000 5000 \n", + "LTS/RECV/1/rcu_pwr_dig_r 1000 5000 \n", + "LTS/RECV/1/rcu_translator_busy_r 1000 5000 \n", + "LTS/RECV/1/rcu_version_r 1000 5000 \n", + "LTS/RECV/1/state 1000 5000 \n", + "LTS/RECV/1/status 1000 5000 \n", + "LTS/RECV/1/rcu_id_r 1000 10000 \n", + "\n", + "Attribute Record Freq Failure Freq \n", + "----------\n", + "LTS/RECV/1/rcu_temperature_r 0.0 1.0 \n", + "LTS/RECV/1/ant_mask_rw 10.0 3.0 \n", + "LTS/RECV/1/clk_enable_pwr_r 0.0 12.0 \n", + "LTS/RECV/1/clk_i2c_status_r 0.0 12.0 \n", + "LTS/RECV/1/clk_pll_error_r 0.0 12.0 \n", + "LTS/RECV/1/clk_pll_locked_r 0.0 12.0 \n", + "LTS/RECV/1/clk_monitor_rate_rw 10.0 3.0 \n", + "LTS/RECV/1/clk_translator_busy_r 0.0 12.0 \n", + "LTS/RECV/1/hba_element_beamformer_delays_r 0.0 12.0 \n", + "LTS/RECV/1/hba_element_beamformer_delays_rw 10.0 3.0 \n", + "LTS/RECV/1/hba_element_led_r 0.0 12.0 \n", + "LTS/RECV/1/hba_element_led_rw 10.0 3.0 \n", + "LTS/RECV/1/hba_element_lna_pwr_r 0.0 12.0 \n", + "LTS/RECV/1/hba_element_lna_pwr_rw 10.0 3.0 \n", + "LTS/RECV/1/hba_element_pwr_r 0.0 12.0 \n", + "LTS/RECV/1/hba_element_pwr_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_adc_lock_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_attenuator_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_attenuator_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_band_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_band_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_i2c_status_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_led0_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_led0_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_led1_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_led1_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_mask_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_monitor_rate_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_pwr_dig_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_translator_busy_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_version_r 0.0 12.0 \n", + "LTS/RECV/1/state 13.0 0.0 \n", + "LTS/RECV/1/status 0.0 13.0 \n", + "LTS/RECV/1/rcu_id_r 0.0 6.0 \n" + ] + } + ], + "source": [ + "attrs = archiver.get_subscriber_attributes()\n", + "print_periods(attrs)\n", + "print()\n", + "print_freq(attrs)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "c3415c09", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Attribute lts/sdp/1/version_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/opcua_missing_attributes_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_wg_amplitude_rw will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_wg_frequency_rw will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_wg_phase_rw will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_enable_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_enable_rw will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_hdr_eth_destination_mac_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_hdr_eth_destination_mac_rw will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_hdr_ip_destination_address_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_hdr_ip_destination_address_rw will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_hdr_udp_destination_port_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_hdr_udp_destination_port_rw will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_scale_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_beamlet_output_scale_rw will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_firmware_version_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_global_node_index_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_hardware_version_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_processing_enable_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_processing_enable_rw will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_signal_input_mean_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_signal_input_rms_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_sdp_info_antenna_band_index_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_sdp_info_block_period_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_sdp_info_f_adc_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_sdp_info_fsub_type_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_sdp_info_nyquist_sampling_zone_index_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_sdp_info_nyquist_sampling_zone_index_rw will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_sdp_info_observation_id_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_sdp_info_observation_id_rw will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_sdp_info_station_id_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_sdp_info_station_id_rw will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_subband_weights_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_subband_weights_rw will not be archived because polling is set to FALSE!\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OFF\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Attribute lts/sdp/1/fpga_wg_amplitude_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_wg_enable_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_wg_enable_rw will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_wg_frequency_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/fpga_wg_phase_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/tr_fpga_mask_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/tr_fpga_mask_rw will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/tr_fpga_communication_error_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/tr_sdp_config_first_fpga_nr_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/tr_sdp_config_nof_beamsets_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/tr_sdp_config_nof_fpgas_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/tr_software_version_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/tr_start_time_r will not be archived because polling is set to FALSE!\n", + "Attribute lts/sdp/1/tr_tod_pps_delta_r will not be archived because polling is set to FALSE!\n" + ] + } + ], + "source": [ + "# Add SDP attributes to load test\n", + "sdp_name = 'STAT/SDP/1'\n", + "d2=DeviceProxy(sdp_name) \n", + "state = str(d2.state())\n", + "print(state)\n", + "archiver.add_attributes_by_device(sdp_name,global_archive_period=5000)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "141c52da", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Device is now in ON state\n" + ] + } + ], + "source": [ + "# Start the SDP device\n", + "if state == \"OFF\":\n", + " time.sleep(1)\n", + " d2.initialise()\n", + " time.sleep(1)\n", + "state = str(d2.state())\n", + "if state == \"STANDBY\":\n", + " d2.on()\n", + "state = str(d2.state())\n", + "if state == \"ON\":\n", + " d2.set_defaults()\n", + " print(\"Device is now in ON state\")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "b53e5b8b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'tango://databaseds:10000/lts/recv/1/rcu_temperature_r': 'Read value for attribute RCU_temperature_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_enable_pwr_r': 'Read value for attribute CLK_Enable_PWR_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_i2c_status_r': 'Read value for attribute CLK_I2C_STATUS_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_pll_error_r': 'Read value for attribute CLK_PLL_error_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_pll_locked_r': 'Read value for attribute CLK_PLL_locked_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_translator_busy_r': 'Read value for attribute CLK_translator_busy_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_beamformer_delays_r': 'Read value for attribute HBA_element_beamformer_delays_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_led_r': 'Read value for attribute HBA_element_led_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_lna_pwr_r': 'Read value for attribute HBA_element_LNA_pwr_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_pwr_r': 'Read value for attribute HBA_element_pwr_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_adc_lock_r': 'Read value for attribute RCU_ADC_lock_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_attenuator_r': 'Read value for attribute RCU_attenuator_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_band_r': 'Read value for attribute RCU_band_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_i2c_status_r': 'Read value for attribute RCU_I2C_STATUS_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_led0_r': 'Read value for attribute RCU_LED0_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_led1_r': 'Read value for attribute RCU_LED1_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_pwr_dig_r': 'Read value for attribute RCU_Pwr_dig_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_translator_busy_r': 'Read value for attribute RCU_translator_busy_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_version_r': 'Read value for attribute RCU_version_R has not been updated',\n", + " 'tango://databaseds:10000/lts/recv/1/status': 'Storing Error: mysql_stmt_bind_param() failed, err=Buffer type is not supported',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_id_r': 'Read value for attribute RCU_ID_R has not been updated',\n", + " 'tango://databaseds:10000/lts/sdp/1/fpga_scrap_r': 'Read value for attribute FPGA_scrap_R has not been updated',\n", + " 'tango://databaseds:10000/lts/sdp/1/fpga_scrap_rw': 'Read value for attribute FPGA_scrap_RW has not been updated',\n", + " 'tango://databaseds:10000/lts/sdp/1/fpga_temp_r': 'Read value for attribute FPGA_temp_R has not been updated',\n", + " 'tango://databaseds:10000/lts/sdp/1/fpga_weights_r': 'Read value for attribute FPGA_weights_R has not been updated',\n", + " 'tango://databaseds:10000/lts/sdp/1/fpga_weights_rw': 'Read value for attribute FPGA_weights_RW has not been updated',\n", + " 'tango://databaseds:10000/lts/sdp/1/tr_tod_r': 'Read value for attribute TR_tod_R has not been updated'}" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Check errors\n", + "err_dict = archiver.get_subscriber_errors()\n", + "err_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "4fed6cf4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Attribute Poll Period Archive Period \n", + "----------\n", + "LTS/RECV/1/rcu_temperature_r 10000 60000 \n", + "LTS/RECV/1/ant_mask_rw 1000 5000 \n", + "LTS/RECV/1/clk_enable_pwr_r 1000 5000 \n", + "LTS/RECV/1/clk_i2c_status_r 1000 5000 \n", + "LTS/RECV/1/clk_pll_error_r 1000 5000 \n", + "LTS/RECV/1/clk_pll_locked_r 1000 5000 \n", + "LTS/RECV/1/clk_monitor_rate_rw 1000 5000 \n", + "LTS/RECV/1/clk_translator_busy_r 1000 5000 \n", + "LTS/RECV/1/hba_element_beamformer_delays_r 1000 5000 \n", + "LTS/RECV/1/hba_element_beamformer_delays_rw 1000 5000 \n", + "LTS/RECV/1/hba_element_led_r 1000 5000 \n", + "LTS/RECV/1/hba_element_led_rw 1000 5000 \n", + "LTS/RECV/1/hba_element_lna_pwr_r 1000 5000 \n", + "LTS/RECV/1/hba_element_lna_pwr_rw 1000 5000 \n", + "LTS/RECV/1/hba_element_pwr_r 1000 5000 \n", + "LTS/RECV/1/hba_element_pwr_rw 1000 5000 \n", + "LTS/RECV/1/rcu_adc_lock_r 1000 5000 \n", + "LTS/RECV/1/rcu_attenuator_r 1000 5000 \n", + "LTS/RECV/1/rcu_attenuator_rw 1000 5000 \n", + "LTS/RECV/1/rcu_band_r 1000 5000 \n", + "LTS/RECV/1/rcu_band_rw 1000 5000 \n", + "LTS/RECV/1/rcu_i2c_status_r 1000 5000 \n", + "LTS/RECV/1/rcu_led0_r 1000 5000 \n", + "LTS/RECV/1/rcu_led0_rw 1000 5000 \n", + "LTS/RECV/1/rcu_led1_r 1000 5000 \n", + "LTS/RECV/1/rcu_led1_rw 1000 5000 \n", + "LTS/RECV/1/rcu_mask_rw 1000 5000 \n", + "LTS/RECV/1/rcu_monitor_rate_rw 1000 5000 \n", + "LTS/RECV/1/rcu_pwr_dig_r 1000 5000 \n", + "LTS/RECV/1/rcu_translator_busy_r 1000 5000 \n", + "LTS/RECV/1/rcu_version_r 1000 5000 \n", + "LTS/RECV/1/state 1000 5000 \n", + "LTS/RECV/1/status 1000 5000 \n", + "LTS/RECV/1/rcu_id_r 1000 10000 \n", + "LTS/SDP/1/fpga_scrap_r 1000 5000 \n", + "LTS/SDP/1/fpga_scrap_rw 1000 5000 \n", + "LTS/SDP/1/fpga_temp_r 1000 5000 \n", + "LTS/SDP/1/fpga_weights_r 1000 5000 \n", + "LTS/SDP/1/fpga_weights_rw 1000 5000 \n", + "LTS/SDP/1/tr_tod_r 1000 5000 \n", + "\n", + "Attribute Record Freq Failure Freq \n", + "----------\n", + "LTS/RECV/1/rcu_temperature_r 0.0 1.0 \n", + "LTS/RECV/1/ant_mask_rw 10.0 3.0 \n", + "LTS/RECV/1/clk_enable_pwr_r 0.0 12.0 \n", + "LTS/RECV/1/clk_i2c_status_r 0.0 12.0 \n", + "LTS/RECV/1/clk_pll_error_r 0.0 12.0 \n", + "LTS/RECV/1/clk_pll_locked_r 0.0 12.0 \n", + "LTS/RECV/1/clk_monitor_rate_rw 10.0 3.0 \n", + "LTS/RECV/1/clk_translator_busy_r 0.0 12.0 \n", + "LTS/RECV/1/hba_element_beamformer_delays_r 0.0 12.0 \n", + "LTS/RECV/1/hba_element_beamformer_delays_rw 10.0 3.0 \n", + "LTS/RECV/1/hba_element_led_r 0.0 12.0 \n", + "LTS/RECV/1/hba_element_led_rw 10.0 3.0 \n", + "LTS/RECV/1/hba_element_lna_pwr_r 0.0 12.0 \n", + "LTS/RECV/1/hba_element_lna_pwr_rw 10.0 3.0 \n", + "LTS/RECV/1/hba_element_pwr_r 0.0 12.0 \n", + "LTS/RECV/1/hba_element_pwr_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_adc_lock_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_attenuator_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_attenuator_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_band_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_band_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_i2c_status_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_led0_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_led0_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_led1_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_led1_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_mask_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_monitor_rate_rw 10.0 3.0 \n", + "LTS/RECV/1/rcu_pwr_dig_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_translator_busy_r 0.0 12.0 \n", + "LTS/RECV/1/rcu_version_r 0.0 12.0 \n", + "LTS/RECV/1/state 13.0 0.0 \n", + "LTS/RECV/1/status 0.0 13.0 \n", + "LTS/RECV/1/rcu_id_r 0.0 6.0 \n", + "LTS/SDP/1/fpga_scrap_r 0.0 12.0 \n", + "LTS/SDP/1/fpga_scrap_rw 0.0 12.0 \n", + "LTS/SDP/1/fpga_temp_r 0.0 12.0 \n", + "LTS/SDP/1/fpga_weights_r 0.0 12.0 \n", + "LTS/SDP/1/fpga_weights_rw 0.0 12.0 \n", + "LTS/SDP/1/tr_tod_r 0.0 12.0 \n" + ] + } + ], + "source": [ + "# Check frequencies\n", + "attrs = archiver.get_subscriber_attributes()\n", + "print_periods(attrs)\n", + "print()\n", + "print_freq(attrs)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "6e495661", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'lts/recv/1/ant_mask_rw'" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Initialise the retriever object and print the archived attributes in the database\n", + "retriever = Retriever()\n", + "# Attribute chosen to be retrieved\n", + "attr_name = 'ant_mask_rw'\n", + "attr_fq_name = str(device_name+'/'+attr_name).lower()\n", + "attr_fq_name" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "304f50f1", + "metadata": {}, + "outputs": [], + "source": [ + "# Retrieve records in the last n hours (works even with decimals)\n", + "\n", + "# Use alternatively one of the following two methods to retrieve data (last n hours or interval)\n", + "records= retriever.get_attribute_value_by_hours(attr_fq_name,hours=0.1)\n", + "#records = retriever.get_attribute_value_by_interval(attr_fq_name,'2021-09-01 16:00:00', '2021-09-01 16:03:00')\n", + "\n", + "if not records:\n", + " print('Empty result!')\n", + "else:\n", + " # Convert DB Array records into Python lists\n", + " data = build_array_from_record(records,records[0].dim_x_r)\n", + " # Extract only the value from the array \n", + " array_values = get_values_from_record(data)\n", + "\n", + "#records\n", + "#data\n", + "#array_values" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "fb2c19f4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[[<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:11:45.984153',recv_time='2021-10-12 16:11:45.985227',insert_time='2021-10-12 16:11:47.172490',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:11:45.984153',recv_time='2021-10-12 16:11:45.985227',insert_time='2021-10-12 16:11:47.172490',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:11:45.984153',recv_time='2021-10-12 16:11:45.985227',insert_time='2021-10-12 16:11:47.172490',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:11:50.983968',recv_time='2021-10-12 16:11:50.985012',insert_time='2021-10-12 16:11:54.941481',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:11:50.983968',recv_time='2021-10-12 16:11:50.985012',insert_time='2021-10-12 16:11:54.941481',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:11:50.983968',recv_time='2021-10-12 16:11:50.985012',insert_time='2021-10-12 16:11:54.941481',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:11:55.984188',recv_time='2021-10-12 16:11:55.985084',insert_time='2021-10-12 16:11:58.436198',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:11:55.984188',recv_time='2021-10-12 16:11:55.985084',insert_time='2021-10-12 16:11:58.436198',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:11:55.984188',recv_time='2021-10-12 16:11:55.985084',insert_time='2021-10-12 16:11:58.436198',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:00.984500',recv_time='2021-10-12 16:12:00.985745',insert_time='2021-10-12 16:12:02.161066',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:00.984500',recv_time='2021-10-12 16:12:00.985745',insert_time='2021-10-12 16:12:02.161066',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:00.984500',recv_time='2021-10-12 16:12:00.985745',insert_time='2021-10-12 16:12:02.161066',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:05.984549',recv_time='2021-10-12 16:12:05.985632',insert_time='2021-10-12 16:12:07.162031',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:05.984549',recv_time='2021-10-12 16:12:05.985632',insert_time='2021-10-12 16:12:07.162031',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:05.984549',recv_time='2021-10-12 16:12:05.985632',insert_time='2021-10-12 16:12:07.162031',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:10.984455',recv_time='2021-10-12 16:12:10.985558',insert_time='2021-10-12 16:12:13.614716',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:10.984455',recv_time='2021-10-12 16:12:10.985558',insert_time='2021-10-12 16:12:13.614716',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:10.984455',recv_time='2021-10-12 16:12:10.985558',insert_time='2021-10-12 16:12:13.614716',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:15.984749',recv_time='2021-10-12 16:12:15.985754',insert_time='2021-10-12 16:12:17.710957',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:15.984749',recv_time='2021-10-12 16:12:15.985754',insert_time='2021-10-12 16:12:17.710957',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:15.984749',recv_time='2021-10-12 16:12:15.985754',insert_time='2021-10-12 16:12:17.710957',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:20.984437',recv_time='2021-10-12 16:12:20.985426',insert_time='2021-10-12 16:12:22.179278',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:20.984437',recv_time='2021-10-12 16:12:20.985426',insert_time='2021-10-12 16:12:22.179278',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:20.984437',recv_time='2021-10-12 16:12:20.985426',insert_time='2021-10-12 16:12:22.179278',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:25.984240',recv_time='2021-10-12 16:12:25.985322',insert_time='2021-10-12 16:12:27.121467',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:25.984240',recv_time='2021-10-12 16:12:25.985322',insert_time='2021-10-12 16:12:27.121467',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:25.984240',recv_time='2021-10-12 16:12:25.985322',insert_time='2021-10-12 16:12:27.121467',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:30.984213',recv_time='2021-10-12 16:12:30.985970',insert_time='2021-10-12 16:12:32.143646',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:30.984213',recv_time='2021-10-12 16:12:30.985970',insert_time='2021-10-12 16:12:32.143646',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:30.984213',recv_time='2021-10-12 16:12:30.985970',insert_time='2021-10-12 16:12:32.143646',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:35.984194',recv_time='2021-10-12 16:12:35.985136',insert_time='2021-10-12 16:12:37.105945',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:35.984194',recv_time='2021-10-12 16:12:35.985136',insert_time='2021-10-12 16:12:37.105945',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:35.984194',recv_time='2021-10-12 16:12:35.985136',insert_time='2021-10-12 16:12:37.105945',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:40.984436',recv_time='2021-10-12 16:12:40.985560',insert_time='2021-10-12 16:12:42.131690',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:40.984436',recv_time='2021-10-12 16:12:40.985560',insert_time='2021-10-12 16:12:42.131690',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:40.984436',recv_time='2021-10-12 16:12:40.985560',insert_time='2021-10-12 16:12:42.131690',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:45.984915',recv_time='2021-10-12 16:12:45.986000',insert_time='2021-10-12 16:12:47.175174',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:45.984915',recv_time='2021-10-12 16:12:45.986000',insert_time='2021-10-12 16:12:47.175174',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:45.984915',recv_time='2021-10-12 16:12:45.986000',insert_time='2021-10-12 16:12:47.175174',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:50.984633',recv_time='2021-10-12 16:12:50.985512',insert_time='2021-10-12 16:12:53.057782',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:50.984633',recv_time='2021-10-12 16:12:50.985512',insert_time='2021-10-12 16:12:53.057782',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:50.984633',recv_time='2021-10-12 16:12:50.985512',insert_time='2021-10-12 16:12:53.057782',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:55.984062',recv_time='2021-10-12 16:12:55.985186',insert_time='2021-10-12 16:12:57.789312',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:55.984062',recv_time='2021-10-12 16:12:55.985186',insert_time='2021-10-12 16:12:57.789312',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:12:55.984062',recv_time='2021-10-12 16:12:55.985186',insert_time='2021-10-12 16:12:57.789312',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:00.984010',recv_time='2021-10-12 16:13:00.985004',insert_time='2021-10-12 16:13:02.085417',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:00.984010',recv_time='2021-10-12 16:13:00.985004',insert_time='2021-10-12 16:13:02.085417',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:00.984010',recv_time='2021-10-12 16:13:00.985004',insert_time='2021-10-12 16:13:02.085417',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:05.984656',recv_time='2021-10-12 16:13:05.985886',insert_time='2021-10-12 16:13:07.114881',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:05.984656',recv_time='2021-10-12 16:13:05.985886',insert_time='2021-10-12 16:13:07.114881',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:05.984656',recv_time='2021-10-12 16:13:05.985886',insert_time='2021-10-12 16:13:07.114881',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:10.983994',recv_time='2021-10-12 16:13:10.984868',insert_time='2021-10-12 16:13:13.955591',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:10.983994',recv_time='2021-10-12 16:13:10.984868',insert_time='2021-10-12 16:13:13.955591',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:10.983994',recv_time='2021-10-12 16:13:10.984868',insert_time='2021-10-12 16:13:13.955591',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:15.984364',recv_time='2021-10-12 16:13:15.985187',insert_time='2021-10-12 16:13:17.261063',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:15.984364',recv_time='2021-10-12 16:13:15.985187',insert_time='2021-10-12 16:13:17.261063',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:15.984364',recv_time='2021-10-12 16:13:15.985187',insert_time='2021-10-12 16:13:17.261063',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:20.984283',recv_time='2021-10-12 16:13:20.985476',insert_time='2021-10-12 16:13:22.137614',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:20.984283',recv_time='2021-10-12 16:13:20.985476',insert_time='2021-10-12 16:13:22.137614',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:20.984283',recv_time='2021-10-12 16:13:20.985476',insert_time='2021-10-12 16:13:22.137614',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:25.984392',recv_time='2021-10-12 16:13:25.985198',insert_time='2021-10-12 16:13:32.429251',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:25.984392',recv_time='2021-10-12 16:13:25.985198',insert_time='2021-10-12 16:13:32.429251',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:25.984392',recv_time='2021-10-12 16:13:25.985198',insert_time='2021-10-12 16:13:32.429251',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:30.984191',recv_time='2021-10-12 16:13:30.985085',insert_time='2021-10-12 16:13:32.691181',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:30.984191',recv_time='2021-10-12 16:13:30.985085',insert_time='2021-10-12 16:13:32.691181',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:30.984191',recv_time='2021-10-12 16:13:30.985085',insert_time='2021-10-12 16:13:32.691181',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:35.984482',recv_time='2021-10-12 16:13:35.985543',insert_time='2021-10-12 16:13:36.243490',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:35.984482',recv_time='2021-10-12 16:13:35.985543',insert_time='2021-10-12 16:13:36.243490',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:35.984482',recv_time='2021-10-12 16:13:35.985543',insert_time='2021-10-12 16:13:36.243490',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:40.984427',recv_time='2021-10-12 16:13:40.985152',insert_time='2021-10-12 16:13:42.666220',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:40.984427',recv_time='2021-10-12 16:13:40.985152',insert_time='2021-10-12 16:13:42.666220',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:40.984427',recv_time='2021-10-12 16:13:40.985152',insert_time='2021-10-12 16:13:42.666220',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:45.984380',recv_time='2021-10-12 16:13:45.985301',insert_time='2021-10-12 16:13:45.987537',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:45.984380',recv_time='2021-10-12 16:13:45.985301',insert_time='2021-10-12 16:13:45.987537',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:45.984380',recv_time='2021-10-12 16:13:45.985301',insert_time='2021-10-12 16:13:45.987537',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:50.984643',recv_time='2021-10-12 16:13:50.985563',insert_time='2021-10-12 16:13:50.987682',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:50.984643',recv_time='2021-10-12 16:13:50.985563',insert_time='2021-10-12 16:13:50.987682',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:50.984643',recv_time='2021-10-12 16:13:50.985563',insert_time='2021-10-12 16:13:50.987682',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:55.983973',recv_time='2021-10-12 16:13:55.984880',insert_time='2021-10-12 16:13:55.986907',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:55.983973',recv_time='2021-10-12 16:13:55.984880',insert_time='2021-10-12 16:13:55.986907',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:13:55.983973',recv_time='2021-10-12 16:13:55.984880',insert_time='2021-10-12 16:13:55.986907',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:00.984719',recv_time='2021-10-12 16:14:00.985552',insert_time='2021-10-12 16:14:03.021740',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:00.984719',recv_time='2021-10-12 16:14:00.985552',insert_time='2021-10-12 16:14:03.021740',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:00.984719',recv_time='2021-10-12 16:14:00.985552',insert_time='2021-10-12 16:14:03.021740',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:05.984695',recv_time='2021-10-12 16:14:05.985635',insert_time='2021-10-12 16:14:05.989173',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:05.984695',recv_time='2021-10-12 16:14:05.985635',insert_time='2021-10-12 16:14:05.989173',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:05.984695',recv_time='2021-10-12 16:14:05.985635',insert_time='2021-10-12 16:14:05.989173',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:10.984766',recv_time='2021-10-12 16:14:10.988322',insert_time='2021-10-12 16:14:10.991811',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:10.984766',recv_time='2021-10-12 16:14:10.988322',insert_time='2021-10-12 16:14:10.991811',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:10.984766',recv_time='2021-10-12 16:14:10.988322',insert_time='2021-10-12 16:14:10.991811',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:15.984611',recv_time='2021-10-12 16:14:15.985512',insert_time='2021-10-12 16:14:15.987666',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:15.984611',recv_time='2021-10-12 16:14:15.985512',insert_time='2021-10-12 16:14:15.987666',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:15.984611',recv_time='2021-10-12 16:14:15.985512',insert_time='2021-10-12 16:14:15.987666',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:20.984560',recv_time='2021-10-12 16:14:20.985450',insert_time='2021-10-12 16:14:20.988027',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:20.984560',recv_time='2021-10-12 16:14:20.985450',insert_time='2021-10-12 16:14:20.988027',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:20.984560',recv_time='2021-10-12 16:14:20.985450',insert_time='2021-10-12 16:14:20.988027',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:25.984529',recv_time='2021-10-12 16:14:25.985516',insert_time='2021-10-12 16:14:28.424397',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:25.984529',recv_time='2021-10-12 16:14:25.985516',insert_time='2021-10-12 16:14:28.424397',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:25.984529',recv_time='2021-10-12 16:14:25.985516',insert_time='2021-10-12 16:14:28.424397',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:30.984777',recv_time='2021-10-12 16:14:30.985744',insert_time='2021-10-12 16:14:30.987772',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:30.984777',recv_time='2021-10-12 16:14:30.985744',insert_time='2021-10-12 16:14:30.987772',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:30.984777',recv_time='2021-10-12 16:14:30.985744',insert_time='2021-10-12 16:14:30.987772',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:37.039585',recv_time='2021-10-12 16:14:37.041323',insert_time='2021-10-12 16:14:37.273810',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:37.039585',recv_time='2021-10-12 16:14:37.041323',insert_time='2021-10-12 16:14:37.273810',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:37.039585',recv_time='2021-10-12 16:14:37.041323',insert_time='2021-10-12 16:14:37.273810',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:37.985042',recv_time='2021-10-12 16:14:37.985943',insert_time='2021-10-12 16:14:37.988002',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:37.985042',recv_time='2021-10-12 16:14:37.985943',insert_time='2021-10-12 16:14:37.988002',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:37.985042',recv_time='2021-10-12 16:14:37.985943',insert_time='2021-10-12 16:14:37.988002',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:41.984482',recv_time='2021-10-12 16:14:41.985286',insert_time='2021-10-12 16:14:41.987331',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:41.984482',recv_time='2021-10-12 16:14:41.985286',insert_time='2021-10-12 16:14:41.987331',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:41.984482',recv_time='2021-10-12 16:14:41.985286',insert_time='2021-10-12 16:14:41.987331',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:46.983903',recv_time='2021-10-12 16:14:46.984621',insert_time='2021-10-12 16:14:46.986645',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:46.983903',recv_time='2021-10-12 16:14:46.984621',insert_time='2021-10-12 16:14:46.986645',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:46.983903',recv_time='2021-10-12 16:14:46.984621',insert_time='2021-10-12 16:14:46.986645',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:51.985363',recv_time='2021-10-12 16:14:51.988993',insert_time='2021-10-12 16:14:51.996470',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:51.985363',recv_time='2021-10-12 16:14:51.988993',insert_time='2021-10-12 16:14:51.996470',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:51.985363',recv_time='2021-10-12 16:14:51.988993',insert_time='2021-10-12 16:14:51.996470',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:56.983913',recv_time='2021-10-12 16:14:56.984669',insert_time='2021-10-12 16:14:56.986431',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:56.983913',recv_time='2021-10-12 16:14:56.984669',insert_time='2021-10-12 16:14:56.986431',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:14:56.983913',recv_time='2021-10-12 16:14:56.984669',insert_time='2021-10-12 16:14:56.986431',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:01.984158',recv_time='2021-10-12 16:15:01.985027',insert_time='2021-10-12 16:15:01.987260',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:01.984158',recv_time='2021-10-12 16:15:01.985027',insert_time='2021-10-12 16:15:01.987260',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:01.984158',recv_time='2021-10-12 16:15:01.985027',insert_time='2021-10-12 16:15:01.987260',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:06.984749',recv_time='2021-10-12 16:15:06.985475',insert_time='2021-10-12 16:15:10.684370',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:06.984749',recv_time='2021-10-12 16:15:06.985475',insert_time='2021-10-12 16:15:10.684370',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:06.984749',recv_time='2021-10-12 16:15:06.985475',insert_time='2021-10-12 16:15:10.684370',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:09.984143',recv_time='2021-10-12 16:15:10.724971',insert_time='2021-10-12 16:15:10.939773',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:09.984143',recv_time='2021-10-12 16:15:10.724971',insert_time='2021-10-12 16:15:10.939773',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:09.984143',recv_time='2021-10-12 16:15:10.724971',insert_time='2021-10-12 16:15:10.939773',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:11.984527',recv_time='2021-10-12 16:15:11.985832',insert_time='2021-10-12 16:15:11.987864',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:11.984527',recv_time='2021-10-12 16:15:11.985832',insert_time='2021-10-12 16:15:11.987864',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:11.984527',recv_time='2021-10-12 16:15:11.985832',insert_time='2021-10-12 16:15:11.987864',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:16.984401',recv_time='2021-10-12 16:15:16.985325',insert_time='2021-10-12 16:15:16.987491',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:16.984401',recv_time='2021-10-12 16:15:16.985325',insert_time='2021-10-12 16:15:16.987491',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:16.984401',recv_time='2021-10-12 16:15:16.985325',insert_time='2021-10-12 16:15:16.987491',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:21.984689',recv_time='2021-10-12 16:15:21.987848',insert_time='2021-10-12 16:15:21.995639',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:21.984689',recv_time='2021-10-12 16:15:21.987848',insert_time='2021-10-12 16:15:21.995639',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:21.984689',recv_time='2021-10-12 16:15:21.987848',insert_time='2021-10-12 16:15:21.995639',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:26.985033',recv_time='2021-10-12 16:15:26.986515',insert_time='2021-10-12 16:15:26.989821',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:26.985033',recv_time='2021-10-12 16:15:26.986515',insert_time='2021-10-12 16:15:26.989821',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:26.985033',recv_time='2021-10-12 16:15:26.986515',insert_time='2021-10-12 16:15:26.989821',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:31.984568',recv_time='2021-10-12 16:15:31.985507',insert_time='2021-10-12 16:15:31.987596',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:31.984568',recv_time='2021-10-12 16:15:31.985507',insert_time='2021-10-12 16:15:31.987596',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:31.984568',recv_time='2021-10-12 16:15:31.985507',insert_time='2021-10-12 16:15:31.987596',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:36.984577',recv_time='2021-10-12 16:15:36.985529',insert_time='2021-10-12 16:15:36.987719',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:36.984577',recv_time='2021-10-12 16:15:36.985529',insert_time='2021-10-12 16:15:36.987719',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:36.984577',recv_time='2021-10-12 16:15:36.985529',insert_time='2021-10-12 16:15:36.987719',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:41.984143',recv_time='2021-10-12 16:15:41.985114',insert_time='2021-10-12 16:15:41.987742',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:41.984143',recv_time='2021-10-12 16:15:41.985114',insert_time='2021-10-12 16:15:41.987742',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:41.984143',recv_time='2021-10-12 16:15:41.985114',insert_time='2021-10-12 16:15:41.987742',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:46.983999',recv_time='2021-10-12 16:15:46.985104',insert_time='2021-10-12 16:15:46.988166',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:46.983999',recv_time='2021-10-12 16:15:46.985104',insert_time='2021-10-12 16:15:46.988166',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:46.983999',recv_time='2021-10-12 16:15:46.985104',insert_time='2021-10-12 16:15:46.988166',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:51.984200',recv_time='2021-10-12 16:15:51.985471',insert_time='2021-10-12 16:15:51.988197',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:51.984200',recv_time='2021-10-12 16:15:51.985471',insert_time='2021-10-12 16:15:51.988197',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:51.984200',recv_time='2021-10-12 16:15:51.985471',insert_time='2021-10-12 16:15:51.988197',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:56.984150',recv_time='2021-10-12 16:15:56.985423',insert_time='2021-10-12 16:15:56.988016',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:56.984150',recv_time='2021-10-12 16:15:56.985423',insert_time='2021-10-12 16:15:56.988016',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:15:56.984150',recv_time='2021-10-12 16:15:56.985423',insert_time='2021-10-12 16:15:56.988016',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:01.984230',recv_time='2021-10-12 16:16:01.985094',insert_time='2021-10-12 16:16:01.987267',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:01.984230',recv_time='2021-10-12 16:16:01.985094',insert_time='2021-10-12 16:16:01.987267',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:01.984230',recv_time='2021-10-12 16:16:01.985094',insert_time='2021-10-12 16:16:01.987267',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:06.983920',recv_time='2021-10-12 16:16:06.984817',insert_time='2021-10-12 16:16:06.986719',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:06.983920',recv_time='2021-10-12 16:16:06.984817',insert_time='2021-10-12 16:16:06.986719',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:06.983920',recv_time='2021-10-12 16:16:06.984817',insert_time='2021-10-12 16:16:06.986719',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:11.984220',recv_time='2021-10-12 16:16:11.985126',insert_time='2021-10-12 16:16:11.986983',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:11.984220',recv_time='2021-10-12 16:16:11.985126',insert_time='2021-10-12 16:16:11.986983',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:11.984220',recv_time='2021-10-12 16:16:11.985126',insert_time='2021-10-12 16:16:11.986983',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:16.983941',recv_time='2021-10-12 16:16:16.984744',insert_time='2021-10-12 16:16:16.986615',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:16.983941',recv_time='2021-10-12 16:16:16.984744',insert_time='2021-10-12 16:16:16.986615',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:16.983941',recv_time='2021-10-12 16:16:16.984744',insert_time='2021-10-12 16:16:16.986615',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:21.984522',recv_time='2021-10-12 16:16:21.985446',insert_time='2021-10-12 16:16:21.987553',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:21.984522',recv_time='2021-10-12 16:16:21.985446',insert_time='2021-10-12 16:16:21.987553',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:21.984522',recv_time='2021-10-12 16:16:21.985446',insert_time='2021-10-12 16:16:21.987553',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:26.984167',recv_time='2021-10-12 16:16:26.985007',insert_time='2021-10-12 16:16:26.986991',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:26.984167',recv_time='2021-10-12 16:16:26.985007',insert_time='2021-10-12 16:16:26.986991',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:26.984167',recv_time='2021-10-12 16:16:26.985007',insert_time='2021-10-12 16:16:26.986991',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:56.984488',recv_time='2021-10-12 16:16:56.985745',insert_time='2021-10-12 16:16:56.987969',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:56.984488',recv_time='2021-10-12 16:16:56.985745',insert_time='2021-10-12 16:16:56.987969',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:16:56.984488',recv_time='2021-10-12 16:16:56.985745',insert_time='2021-10-12 16:16:56.987969',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:17:01.984591',recv_time='2021-10-12 16:17:01.987665',insert_time='2021-10-12 16:17:01.995973',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:17:01.984591',recv_time='2021-10-12 16:17:01.987665',insert_time='2021-10-12 16:17:01.995973',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:17:01.984591',recv_time='2021-10-12 16:17:01.987665',insert_time='2021-10-12 16:17:01.995973',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:17:06.985081',recv_time='2021-10-12 16:17:06.988687',insert_time='2021-10-12 16:17:06.996786',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:17:06.985081',recv_time='2021-10-12 16:17:06.988687',insert_time='2021-10-12 16:17:06.996786',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:17:06.985081',recv_time='2021-10-12 16:17:06.988687',insert_time='2021-10-12 16:17:06.996786',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:17:11.984285',recv_time='2021-10-12 16:17:11.985949',insert_time='2021-10-12 16:17:11.989505',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:17:11.984285',recv_time='2021-10-12 16:17:11.985949',insert_time='2021-10-12 16:17:11.989505',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:17:11.984285',recv_time='2021-10-12 16:17:11.985949',insert_time='2021-10-12 16:17:11.989505',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:17:16.984731',recv_time='2021-10-12 16:17:16.985852',insert_time='2021-10-12 16:17:16.988141',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:17:16.984731',recv_time='2021-10-12 16:17:16.985852',insert_time='2021-10-12 16:17:16.988141',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:17:16.984731',recv_time='2021-10-12 16:17:16.985852',insert_time='2021-10-12 16:17:16.988141',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>],\n", + " [<Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:17:21.983865',recv_time='2021-10-12 16:17:21.985019',insert_time='2021-10-12 16:17:21.987370',idx='0',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:17:21.983865',recv_time='2021-10-12 16:17:21.985019',insert_time='2021-10-12 16:17:21.987370',idx='1',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>,\n", + " <Array_Boolean_RW(att_conf_id='1',data_time='2021-10-12 16:17:21.983865',recv_time='2021-10-12 16:17:21.985019',insert_time='2021-10-12 16:17:21.987370',idx='2',dim_x_r='3',dim_y_r='32',value_r='1',dim_x_w='3',dim_y_w='32',value_w='1',quality='0',att_error_desc_id='None')>]]" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "eb97ee97", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Attribute lts/recv/1/rcu_temperature_r removed!\n", + "Attribute lts/recv/1/clk_enable_pwr_r removed!\n", + "Attribute lts/recv/1/clk_i2c_status_r removed!\n", + "Attribute lts/recv/1/clk_pll_error_r removed!\n", + "Attribute lts/recv/1/clk_pll_locked_r removed!\n", + "Attribute lts/recv/1/clk_translator_busy_r removed!\n", + "Attribute lts/recv/1/hba_element_beamformer_delays_r removed!\n", + "Attribute lts/recv/1/hba_element_led_r removed!\n", + "Attribute lts/recv/1/hba_element_lna_pwr_r removed!\n", + "Attribute lts/recv/1/hba_element_pwr_r removed!\n", + "Attribute lts/recv/1/rcu_adc_lock_r removed!\n", + "Attribute lts/recv/1/rcu_attenuator_r removed!\n", + "Attribute lts/recv/1/rcu_band_r removed!\n", + "Attribute lts/recv/1/rcu_i2c_status_r removed!\n", + "Attribute lts/recv/1/rcu_led0_r removed!\n", + "Attribute lts/recv/1/rcu_led1_r removed!\n", + "Attribute lts/recv/1/rcu_pwr_dig_r removed!\n", + "Attribute lts/recv/1/rcu_translator_busy_r removed!\n", + "Attribute lts/recv/1/rcu_version_r removed!\n", + "Attribute lts/recv/1/rcu_id_r removed!\n", + "Attribute lts/recv/1/status removed!\n", + "Attribute lts/sdp/1/fpga_scrap_r removed!\n", + "Attribute lts/sdp/1/fpga_scrap_rw removed!\n", + "Attribute lts/sdp/1/fpga_weights_r removed!\n", + "Attribute lts/sdp/1/fpga_weights_rw removed!\n" + ] + } + ], + "source": [ + "#archiver.remove_attribute_from_archiver('STAT/recv/1/rcu_temperature_r')\n", + "archiver.remove_attributes_in_error()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "a554cff4", + "metadata": {}, + "outputs": [], + "source": [ + "d.off()\n", + "d2.off()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71fabd37", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "StationControl", + "language": "python", + "name": "stationcontrol" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/jupyter-notebooks/Docker_notebook.ipynb b/jupyter-notebooks/Docker_notebook.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..6df68c95bff37d25ccbb5ada0fabd4850b07386d --- /dev/null +++ b/jupyter-notebooks/Docker_notebook.ipynb @@ -0,0 +1,139 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "waiting-chance", + "metadata": {}, + "outputs": [], + "source": [ + "import time" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "moving-alexandria", + "metadata": {}, + "outputs": [], + "source": [ + "d=DeviceProxy(\"STAT/Docker/1\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ranking-aluminum", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Device is now in on state\n" + ] + } + ], + "source": [ + "state = str(d.state())\n", + "\n", + "\n", + "if state == \"OFF\" or state == \"FAULT\":\n", + " d.initialise()\n", + " time.sleep(1)\n", + "state = str(d.state())\n", + "if state == \"STANDBY\":\n", + " d.on()\n", + "state = str(d.state())\n", + "if state == \"ON\":\n", + " print(\"Device is now in on state\")\n", + "else:\n", + " print(\"warning, expected device to be in on state, is: \", state)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0caa8146", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "version_R *L2SS-379-docker-device [0c12e90b1c311df82edf9ebd0b0e9a3a530f0a81]\n", + "archiver_maria_db_R True\n", + "archiver_maria_db_RW False\n", + "databaseds_R True\n", + "databaseds_RW False\n", + "device_recv_R False\n", + "device_recv_RW False\n", + "device_sdp_R True\n", + "device_sdp_RW False\n", + "device_sst_R True\n", + "device_sst_RW False\n", + "device_xst_R False\n", + "device_xst_RW False\n", + "device_unb2_R True\n", + "device_unb2_RW False\n", + "device_docker_R True\n", + "dsconfig_R True\n", + "dsconfig_RW False\n", + "elk_R True\n", + "elk_RW False\n", + "grafana_R True\n", + "grafana_RW False\n", + "hdbpp_cm_R True\n", + "hdbpp_cm_RW False\n", + "hdbpp_es_R True\n", + "hdbpp_es_RW False\n", + "itango_R True\n", + "itango_RW False\n", + "jupyter_R True\n", + "jupyter_RW False\n", + "prometheus_R True\n", + "prometheus_RW False\n", + "tangodb_R True\n", + "tangodb_RW False\n", + "tango_prometheus_exporter_R False\n", + "tango_prometheus_exporter_RW False\n", + "tango_rest_R True\n", + "tango_rest_RW False\n", + "State <function __get_command_func.<locals>.f at 0x7f84a8b4fb70>\n", + "Status <function __get_command_func.<locals>.f at 0x7f84a8b4fb70>\n" + ] + } + ], + "source": [ + "attr_names = d.get_attribute_list()\n", + "\n", + "\n", + "for i in attr_names:\n", + " exec(\"value = print(i, d.{})\".format(i))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "StationControl", + "language": "python", + "name": "stationcontrol" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/jupyter-notebooks/Home.ipynb b/jupyter-notebooks/Home.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..1b6001f9e5a87a0d626e310cc8038ede2d5f589f --- /dev/null +++ b/jupyter-notebooks/Home.ipynb @@ -0,0 +1,166 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e051e48d", + "metadata": {}, + "source": [ + "# Welcome to your LOFAR2.0 station!\n", + "\n", + "The following interfaces are available to you, on the same host as this notebook, but on different ports:\n", + "\n", + "|Interface |Subsystem |Port|Credentials |\n", + "|----------|----------|----|--------------|\n", + "|Scripting |Jupyter |8888| |\n", + "|Monitoring|Grafana |3000|admin/admin |\n", + "|Logs |Kibana |5601| |\n", + "|ReST |tango-rest|8080|tango-cs/tango|\n", + "\n", + "Below are codes to manage the station at high level. For more detailed status information, look in Grafana." + ] + }, + { + "cell_type": "markdown", + "id": "32ae8bcf", + "metadata": {}, + "source": [ + "## (Re)boot station\n", + "The code below is used to:\n", + "* Reboot all station software\n", + "* Reset the hardware configuration" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38037a71", + "metadata": {}, + "outputs": [], + "source": [ + "# Restart boot device itself\n", + "boot.off()\n", + "assert boot.state() == DevState.OFF, boot.state()\n", + "\n", + "boot.initialise()\n", + "assert boot.state() == DevState.STANDBY, boot.state()\n", + "\n", + "boot.on()\n", + "assert boot.state() == DevState.ON, boot.state()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21aba361", + "metadata": {}, + "outputs": [], + "source": [ + "# Request to reinitialise the station.\n", + "#\n", + "# WARNING: This will reset settings across the station!\n", + "boot.initialise_station()\n", + "assert boot.state() != DevState.FAULT" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c00b465a", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "\n", + "while boot.initialising_station_R:\n", + " print(f\"Still initialising station. {boot.initialisation_progress_R}% complete. State: {boot.initialisation_status_R}\")\n", + " time.sleep(1)\n", + "\n", + "if boot.initialisation_progress_R == 100:\n", + " print(\"Done initialising station.\")\n", + "else:\n", + " print(f\"Failed to initialise station: {boot.initialisation_status_R}\")" + ] + }, + { + "cell_type": "markdown", + "id": "b444b751", + "metadata": {}, + "source": [ + "## Inspect Docker status\n", + "Docker containers that are not running will not provide any functionality, and are ignored when the station is rebooted." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b09f9da", + "metadata": {}, + "outputs": [], + "source": [ + "container_status = {attr_name: getattr(docker, attr_name)\n", + " for attr_name in docker.get_attribute_list()\n", + " if attr_name.endswith(\"_R\")\n", + " and attr_name != 'version_R'}\n", + "\n", + "not_running_containers = [container for container, running in container_status.items() if running is False]\n", + "\n", + "if not not_running_containers:\n", + " print(\"All docker containers are running\")\n", + "else:\n", + " print(f\"Docker containers that are NOT running: {not_running_containers}\")" + ] + }, + { + "cell_type": "markdown", + "id": "55f3981d", + "metadata": {}, + "source": [ + "## Inspect Device status\n", + "Check whether all software devices are indeed up and running." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "637e6e22", + "metadata": {}, + "outputs": [], + "source": [ + "for d in devices:\n", + " try:\n", + " print(f\"Device {d.dev_name()} is in state {d.state()}\")\n", + " except ConnectionFailed as e:\n", + " print(f\"Device {d.dev_name()} is in state DOWN: {e.args[0].desc}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23008885", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "StationControl", + "language": "python", + "name": "stationcontrol" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/jupyter-notebooks/PCC_notebook.ipynb b/jupyter-notebooks/PCC_notebook.ipynb deleted file mode 100644 index f0dd0f9bedae6261c0524b03898491b72eeac1b2..0000000000000000000000000000000000000000 --- a/jupyter-notebooks/PCC_notebook.ipynb +++ /dev/null @@ -1,173 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "funded-deputy", - "metadata": {}, - "outputs": [], - "source": [ - "import time" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "bridal-mumbai", - "metadata": {}, - "outputs": [], - "source": [ - "d=DeviceProxy(\"LTS/PCC/1\")" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "subjective-conference", - "metadata": {}, - "outputs": [], - "source": [ - "state = str(d.state())\n", - "\n", - "if state == \"OFF\":\n", - " d.initialise()\n", - " time.sleep(1)\n", - "state = str(d.state())\n", - "if state == \"STANDBY\":\n", - " d.on()\n", - "state = str(d.state())\n", - "if state == \"ON\":\n", - " print(\"Device is now in on state\")\n" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "liable-thesaurus", - "metadata": {}, - "outputs": [ - { - "ename": "DevFailed", - "evalue": "DevFailed[\nDevError[\n desc = Read value for attribute RCU_mask_RW has not been updated\n origin = Device_3Impl::read_attributes_no_except\n reason = API_AttrValueNotSet\nseverity = ERR]\n\nDevError[\n desc = Failed to read_attribute on device lts/pcc/1, attribute RCU_mask_RW\n origin = DeviceProxy::read_attribute()\n reason = API_AttributeFailed\nseverity = ERR]\n]", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mDevFailed\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m<ipython-input-4-aafae2adcd98>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m values = [[d.RCU_mask_RW, \"RCU_mask_RW\"],\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mAnt_mask_RW\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\"Ant_mask_RW\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mRCU_attenuator_R\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\"RCU_attenuator_R\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mRCU_attenuator_RW\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\"RCU_attenuator_RW\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mRCU_band_R\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\"RCU_band_R\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__DeviceProxy__getattr\u001b[0;34m(self, name)\u001b[0m\n\u001b[1;32m 342\u001b[0m \u001b[0mattr_info\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__get_attr_cache\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname_l\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 343\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mattr_info\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 344\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m__get_attribute_value\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mattr_info\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 345\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 346\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__get_attribute_value\u001b[0;34m(self, attr_info, name)\u001b[0m\n\u001b[1;32m 281\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__get_attribute_value\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mattr_info\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 282\u001b[0m \u001b[0m_\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0menum_class\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mattr_info\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 283\u001b[0;31m \u001b[0mattr_value\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 284\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0menum_class\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 285\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0menum_class\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mattr_value\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/green.py\u001b[0m in \u001b[0;36mgreener\u001b[0;34m(obj, *args, **kwargs)\u001b[0m\n\u001b[1;32m 193\u001b[0m \u001b[0mgreen_mode\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0maccess\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'green_mode'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 194\u001b[0m \u001b[0mexecutor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_object_executor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgreen_mode\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 195\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mexecutor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwait\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mwait\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimeout\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mtimeout\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 196\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 197\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mgreener\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/green.py\u001b[0m in \u001b[0;36mrun\u001b[0;34m(self, fn, args, kwargs, wait, timeout)\u001b[0m\n\u001b[1;32m 107\u001b[0m \u001b[0;31m# Sychronous (no delegation)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 108\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0masynchronous\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0min_executor_context\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 109\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 110\u001b[0m \u001b[0;31m# Asynchronous delegation\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 111\u001b[0m \u001b[0maccessor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdelegate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__DeviceProxy__read_attribute\u001b[0;34m(self, value, extract_as)\u001b[0m\n\u001b[1;32m 439\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 440\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__DeviceProxy__read_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mextract_as\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mExtractAs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mNumpy\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 441\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m__check_read_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_read_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mextract_as\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 442\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 443\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__check_read_attribute\u001b[0;34m(dev_attr)\u001b[0m\n\u001b[1;32m 155\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__check_read_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdev_attr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 156\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mdev_attr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mhas_failed\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 157\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mDevFailed\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mdev_attr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_err_stack\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 158\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mdev_attr\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 159\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mDevFailed\u001b[0m: DevFailed[\nDevError[\n desc = Read value for attribute RCU_mask_RW has not been updated\n origin = Device_3Impl::read_attributes_no_except\n reason = API_AttrValueNotSet\nseverity = ERR]\n\nDevError[\n desc = Failed to read_attribute on device lts/pcc/1, attribute RCU_mask_RW\n origin = DeviceProxy::read_attribute()\n reason = API_AttributeFailed\nseverity = ERR]\n]" - ] - } - ], - "source": [ - "\n", - "values = [[d.RCU_mask_RW, \"RCU_mask_RW\"],\n", - "[d.Ant_mask_RW,\"Ant_mask_RW\"],\n", - "[d.RCU_attenuator_R,\"RCU_attenuator_R\"],\n", - "[d.RCU_attenuator_RW,\"RCU_attenuator_RW\"],\n", - "[d.RCU_band_R,\"RCU_band_R\"],\n", - "[d.RCU_band_RW,\"RCU_band_RW\"],\n", - "[d.RCU_temperature_R,\"RCU_temperature_R\"],\n", - "[d.RCU_Pwr_dig_R,\"RCU_Pwr_dig_R\"],\n", - "[d.RCU_LED0_R,\"RCU_LED0_R\"],\n", - "[d.RCU_LED0_RW,\"RCU_LED0_RW\"],\n", - "[d.RCU_ADC_lock_R,\"RCU_ADC_lock_R\"],\n", - "[d.RCU_ADC_SYNC_R,\"RCU_ADC_SYNC_R\"],\n", - "[d.RCU_ADC_JESD_R,\"RCU_ADC_JESD_R\"],\n", - "[d.RCU_ADC_CML_R,\"RCU_ADC_CML_R\"],\n", - "[d.RCU_OUT1_R,\"RCU_OUT1_R\"],\n", - "[d.RCU_OUT2_R,\"RCU_OUT2_R\"],\n", - "[d.RCU_ID_R,\"RCU_ID_R\"],\n", - "[d.RCU_version_R,\"RCU_version_R\"],\n", - "[d.HBA_element_beamformer_delays_R,\"HBA_element_beamformer_delays_R\"],\n", - "[d.HBA_element_beamformer_delays_RW,\"HBA_element_beamformer_delays_RW\"],\n", - "[d.HBA_element_pwr_R,\"HBA_element_pwr_R\"],\n", - "[d.HBA_element_pwr_RW,\"HBA_element_pwr_RW\"],\n", - "[d.RCU_monitor_rate_RW,\"RCU_monitor_rate_RW\"]]\n", - "\n", - "\n", - "for i in values:\n", - " print(\"🟦🟦🟦\", i[1], \": \", i[0])\n" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "charitable-subject", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[False False False False False False False False False False False False\n", - " False False False False False False False False False False False False\n", - " False False False False False False False False]\n", - "current monitoring rate: 0.0, setting to 1.0\n", - "new monitoring rate is: 1.0\n" - ] - } - ], - "source": [ - "d.RCU_mask_RW = [False, False, False, False, False, False, False, False, False, False, False, False,\n", - " False, False, False, False, False, False, False, False, False, False, False, False,\n", - " False, False, False, False, False, False, False, False,]\n", - "time.sleep(1)\n", - "print(d.RCU_mask_RW)\n", - "\n", - "monitor_rate = d.RCU_monitor_rate_RW\n", - "print(\"current monitoring rate: {}, setting to {}\".format(monitor_rate, monitor_rate + 1))\n", - "d.RCU_monitor_rate_RW = monitor_rate + 1\n", - "time.sleep(2)\n", - "print(\"new monitoring rate is: {}\".format(d.RCU_monitor_rate_RW))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "impressive-request", - "metadata": {}, - "outputs": [], - "source": [ - "attr_names = d.get_attribute_list()\n", - "\n", - "for i in attr_names:\n", - " exec(\"value = print(i, d.{})\".format(i))\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "conditional-scale", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "StationControl", - "language": "python", - "name": "stationcontrol" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/jupyter-notebooks/RECV_archive_all_attributes.ipynb b/jupyter-notebooks/RECV_archive_all_attributes.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..384d1522153c46ec6f1ebf96f0593830cecf75e7 --- /dev/null +++ b/jupyter-notebooks/RECV_archive_all_attributes.ipynb @@ -0,0 +1,382 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "b14a15ae", + "metadata": {}, + "outputs": [], + "source": [ + "import sys, time\n", + "import numpy as np\n", + "sys.path.append('/hosthome/tango/devices')\n", + "from toolkit.archiver import *\n", + "from matplotlib import pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1514b0cd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Attribute lts/recv/1/clk_enable_pwr_r not found!\n", + "Attribute lts/recv/1/clk_i2c_status_r not found!\n", + "Attribute lts/recv/1/clk_pll_error_r not found!\n", + "Attribute lts/recv/1/clk_pll_locked_r not found!\n", + "Attribute lts/recv/1/clk_translator_busy_r not found!\n", + "Device LTS/SDP/1 offline\n", + "Device LTS/SST/1 offline\n", + "Device LTS/UNB2/1 not found\n" + ] + } + ], + "source": [ + "# Apply the chosen JSON configuration file in directory toolkit/archiver_config/\n", + "archiver = Archiver(selector_filename='lofar2.json')" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "03dafaed", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lofar2.json\n" + ] + }, + { + "data": { + "text/plain": [ + "{'LTS/RECV/1': {'environment': 'production',\n", + " 'include': ['rcu_temperature_r'],\n", + " 'exclude': ['CLK_Enable_PWR_R',\n", + " 'CLK_I2C_STATUS_R',\n", + " 'CLK_PLL_error_R',\n", + " 'CLK_PLL_locked_R',\n", + " 'CLK_translator_busy_R']},\n", + " 'LTS/SDP/1': {'environment': 'development', 'include': [], 'exclude': []},\n", + " 'LTS/SST/1': {'environment': 'development', 'include': [], 'exclude': []},\n", + " 'LTS/UNB2/1': {'environment': 'development', 'include': [], 'exclude': []}}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Print the configuration file (as a dictionary)\n", + "selector = archiver.selector\n", + "print(selector.filename)\n", + "env_dict = selector.get_dict()\n", + "env_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "8720f9e7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ON\n" + ] + } + ], + "source": [ + "device_name = 'STAT/RECV/1'\n", + "d=DeviceProxy(device_name) \n", + "state = str(d.state())\n", + "print(state)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "6a9c4f4c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Device is now in ON state\n" + ] + } + ], + "source": [ + "# Start the device\n", + "if state == \"OFF\":\n", + " time.sleep(1)\n", + " d.initialise()\n", + " time.sleep(1)\n", + "state = str(d.state())\n", + "if state == \"STANDBY\":\n", + " d.on()\n", + "state = str(d.state())\n", + "if state == \"ON\":\n", + " print(\"Device is now in ON state\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b12e8887", + "metadata": {}, + "outputs": [], + "source": [ + "# Turn off the device\n", + "d.off()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0989fa30", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('tango://databaseds:10000/lts/recv/1/ant_mask_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/clk_monitor_rate_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_beamformer_delays_r',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_beamformer_delays_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_led_r',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_led_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_lna_pwr_r',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_lna_pwr_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_pwr_r',\n", + " 'tango://databaseds:10000/lts/recv/1/hba_element_pwr_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_adc_lock_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_attenuator_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_attenuator_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_band_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_band_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_i2c_status_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_id_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_led0_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_led0_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_led1_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_led1_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_mask_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_monitor_rate_rw',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_pwr_dig_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_temperature_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_translator_busy_r',\n", + " 'tango://databaseds:10000/lts/recv/1/rcu_version_r')" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Print the attributes currently managed by the event subscriber\n", + "archiver.get_subscriber_attributes()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a906823c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[<Attribute(fullname='tango://databaseds:10000/lts/recv/1/rcu_temperature_r',data_type ='39',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='rcu_temperature_r')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/ant_mask_rw',data_type ='4',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='ant_mask_rw')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/clk_monitor_rate_rw',data_type ='26',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='clk_monitor_rate_rw')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/hba_element_beamformer_delays_r',data_type ='27',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='hba_element_beamformer_delays_r')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/hba_element_beamformer_delays_rw',data_type ='28',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='hba_element_beamformer_delays_rw')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/hba_element_led_r',data_type ='27',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='hba_element_led_r')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/hba_element_led_rw',data_type ='28',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='hba_element_led_rw')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/hba_element_lna_pwr_r',data_type ='27',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='hba_element_lna_pwr_r')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/hba_element_lna_pwr_rw',data_type ='28',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='hba_element_lna_pwr_rw')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/hba_element_pwr_r',data_type ='27',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='hba_element_pwr_r')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/hba_element_pwr_rw',data_type ='28',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='hba_element_pwr_rw')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/rcu_adc_lock_r',data_type ='27',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='rcu_adc_lock_r')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/rcu_attenuator_r',data_type ='27',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='rcu_attenuator_r')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/rcu_attenuator_rw',data_type ='28',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='rcu_attenuator_rw')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/rcu_band_r',data_type ='27',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='rcu_band_r')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/rcu_band_rw',data_type ='28',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='rcu_band_rw')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/rcu_i2c_status_r',data_type ='27',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='rcu_i2c_status_r')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/rcu_id_r',data_type ='27',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='rcu_id_r')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/rcu_led0_r',data_type ='3',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='rcu_led0_r')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/rcu_led0_rw',data_type ='4',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='rcu_led0_rw')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/rcu_led1_r',data_type ='3',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='rcu_led1_r')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/rcu_led1_rw',data_type ='4',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='rcu_led1_rw')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/rcu_mask_rw',data_type ='4',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='rcu_mask_rw')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/rcu_monitor_rate_rw',data_type ='26',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='rcu_monitor_rate_rw')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/rcu_pwr_dig_r',data_type ='3',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='rcu_pwr_dig_r')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/rcu_translator_busy_r',data_type ='1',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='rcu_translator_busy_r')>,\n", + " <Attribute(fullname='tango://databaseds:10000/lts/recv/1/rcu_version_r',data_type ='43',ttl='0',facility ='tango://databaseds:10000',domain ='lts',family ='recv',member ='1',name ='rcu_version_r')>]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Initialise the retriever object and print the archived attributes in the database\n", + "retriever = Retriever()\n", + "retriever.get_all_archived_attributes()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "770d6dbc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'lts/recv/1/rcu_temperature_r'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Attribute chosen to be retrieved\n", + "attr_name = 'rcu_temperature_r'\n", + "attr_fq_name = str(device_name+'/'+attr_name).lower()\n", + "attr_fq_name" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "3734554e", + "metadata": {}, + "outputs": [], + "source": [ + "# Retrieve records in the last n hours (works even with decimals)\n", + "\n", + "# Use alternatively one of the following two methods to retrieve data (last n hours or interval)\n", + "records= retriever.get_attribute_value_by_hours(attr_fq_name,hours=0.01)\n", + "#records = retriever.get_attribute_value_by_interval(attr_fq_name,'2021-09-01 16:00:00', '2021-09-01 16:03:00')\n", + "\n", + "if not records:\n", + " print('Empty result!')\n", + "else:\n", + " # Convert DB Array records into Python lists\n", + " data = build_array_from_record(records,records[0].dim_x_r)\n", + " # Extract only the value from the array \n", + " array_values = get_values_from_record(data)\n", + "\n", + "#records\n", + "#data\n", + "#array_values" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "edb9f117", + "metadata": {}, + "outputs": [], + "source": [ + "# Extract and process timestamps for plotting purposes\n", + "def get_timestamps(data,strformat):\n", + " timestamps = []\n", + " for i in range(len(data)):\n", + " timestamps.append(data[i][0].recv_time.strftime(strformat))\n", + " return timestamps\n", + "timestamps = get_timestamps(data,\"%Y-%m-%d %X\")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "112962a0", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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AIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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OFbdAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMUd0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxR3QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzFHdAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMUd0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxR3QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzFHdAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMUd0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxR3QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzFHdAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMUd0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxR3QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzFHdAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMUd0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxR3QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzFHdAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMUd0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxR3QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzFHdAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMUd0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxR3QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzFHdAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMUd0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxR3QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzFHdAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMUd0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxR3QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzFHdAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMUd0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxR3QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzFHdAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMUd0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxR3QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzFHdAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMUd0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxR3QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzFHdAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMUd0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxR3QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzFHdAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMUd0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxR3QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzFHdAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMUd0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxR3QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzFHdAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMUd0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxR3QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzFHdAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMUd0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxR3QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAD6mNUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQxqwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6GNWBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQx6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCPWR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB+zOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPmZ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9zOoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPqY1QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9DGrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoY1YHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDHrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoI9ZHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH7M6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA+ZnUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH3M6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+pjVAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAAAAEAfszoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgD5mdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfczqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6mNUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQxqwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6GNWBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQx6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCPWR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB+zOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPmZ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9zOoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPqY1QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9DGrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoY1YHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDHrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoI9ZHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH7M6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAfczqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6mNUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQxqwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6GNWBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQx6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCPWR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB+zOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPmZ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9zOoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPqY1QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9DGrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoY1YHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDHrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoI9ZHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH7M6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA+ZnUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH3M6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAHAsVNXZVXV1VV1fVe/bXDuwuXZdVb14lzMXVNWvquq1OzzzrKq6tqpuqKpTq+qkqvpOVV1eVR/fOntyVR2sqquq6uLNtfOq6sqq+nFVXbR1/mnvrapWVRdW1aVV9cGts2/evPfip1z7ZlX9pqpO3GH3HZv3nr/5/pKq+s/Ns//1+f6PAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB/DntGDwAAAAAAAAAAAAAAAAAAAAAAOBZ677cmuTVJqurmJNcleU/v/d1VdXaSA733K7bP9N6/UVUv3OWxH0pyIMlpm8+/SHJL7/3aqrq+ql7Ue39o8/7DSc7ZPPumqmq990s33y9Lcs3W3u33vjPJy5P8McmhzX2XJvl87/1nVXUoyUefcv+nq2r/k9+r6i1Jzui9X5Pk4STHJ7lz8+dPJ/m/JCcm+Z/d/odVdUqSU7Yun5kkR448nkceeWS3W5nYo48+9rTfrEXftem7Nn3Xp/Ha9F2bvmvTd30ar03ftem7Nn3Xp/Ha9F2bvuvTeG36rk3ftem7Po3Xpu/a9F2bvuvTeG36rk3ftem7Po3Xpu/a9F3bkSOPj54AAAAAAAAAAAAAAAAAAAAAAMAE9oweAAAAAAAAAAAAAAAAAAAAAABwLFXVBUkObr4+sfl9V5KzdjnzjHrvT1TVXUleluTLSb5aVa9Lsj/JS5I8tPX+tyb5de/96Ob7iUle2nv/3bO86lVJftl7v6yqvp/klt77ec9l42bn7Ulu33x9fZKe5AdVdUuS1yb5VJK7k1yR5H27POYDSb600x/uu//+3PH7PzzXOUzo0OHDoyfwd6Tv2vRdm77r03ht+q5N37Xpuz6N16bv2vRdm77r03ht+q5N3/VpvDZ916bv2vRdn8Zr03dt+q5N3/VpvDZ916bv2vRdn8Zr03dt+q7pvvvvHz0BAAAAAAAAAAAAAAAAAAAAAIAJ7Bk9AAAAAAAAAAAAAAAAAAAAAADgWKmqzyS5u/f+w82ltvn9iiT37HJm+xnvT/KGJN9K0qvqBU/e33v/S5JPbs59L8m9VXVJknt771+rqrOTvC3JBU955IEkNz6H+fckObr5/HhVtd770We6YTdP3ldVDybZu3n2A0ke2XzfzVVJfrR17cwkB0/avz9nnH7a85nDP7hHH30shw4fziknn5x9+44fPYe/MX3Xpu/a9F2fxmvTd236rk3f9Wm8Nn3Xpu/a9F2fxmvTd236rk/jtem7Nn3Xpu/6NF6bvmvTd236rk/jtem7Nn3Xpu/6NF6bvmvTd21/euCB0RMAAAAAAAAAAAAAAAAAAAAAAJjAntEDAAAAAAAAAAAAAAAAAAAAAACOhao6kOScJLdX1St77xcmubmqrkxyQpKP7XSmqs5J8o4kr66qr/fer01y7eaZVya5OslxSb5QVfuSXJ6kJbmp9/5Qkk9szp6a5IYk301yRVV9qvf+cJJ3bX629z7tvZv7vl1Vb0zy89770aq6LMnnkpyc5ItJXlNVH+69X15V5yd5U5JLquorSU5PckaSnyb5bJInkvy29/7nqvqPJF9P8tck/7Xb/7D3fijJoa2dSZK9e4/LCSec8MwRmNq+fcdrvDB916bv2vRdn8Zr03dt+q5N3/VpvDZ916bv2vRdn8Zr03dt+q5P47XpuzZ916bv+jRem75r03dt+q5P47XpuzZ916bv+jRem75r03dNe/ceN3oCAAAAAAAAAAAAAAAAAAAAAAAT2DN6AAAAAAAAAAAAAAAAAAAAAADAsdB7vzHJjc9ybaczB5Mc3OWZtyW5bevyubucvTPJSTtcf/su53d677lbZz6y+fhgkvdu/e3iJBc/5dLdSX6y+XzO1tn/TvL+nXYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGxrowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI82egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA82ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjzZ6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADzaKMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPNnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPNoowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/D979BNy6Vnecfy6LieTaVDoJpEmVYzpwqoU6ariQoXQTUWEdjNUChEq1NY/aNpUbFEU/5QGKsTEgOJAiIaCWgXTbgQbqNBS6LKLVlsjmYEYiq3BJCYkt5sTmBzmTQbRuXrdfD7wcs553vt5ni/8AAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJijugMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmKO6AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYo7oDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAw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AAAAAAAAAAAAAAAAAAAAgD5mdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfczqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6mNUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQxqwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6GNWBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQx6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCPWR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB+zOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPmZ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9zOoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPqY1QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9DGrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoY1YHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDHrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoI9ZHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH7M6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA+ZnUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAfczqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6mNUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQxqwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6GNWBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQx6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCPWR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB+zOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPmZ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9zOoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPqY1QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9DGrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoY1YHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDHrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoI9ZHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH7M6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA+ZnUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH3M6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAA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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI82OgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAebTRAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPNoowMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmEcbHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPNjoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHm00QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm10AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADzaKMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJhHGx0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza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AAAAAAAAAAD6mNUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQxqwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6GNWBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQx6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCPWR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB+zOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPmZ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9zOoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPqY1QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9DGrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoY1YHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDHrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoI9ZHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH7M6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA+ZnUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH3M6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+pjVAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxjGz0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHlsowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI9t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5rGNHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPLbRAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYxzZ6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz2EYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAe2+gBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxjGz0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHlsowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI9t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5rGNHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPLbRAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYxzZ6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz2EYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAe2+gBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxjGz0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHlsowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI9t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5rGNHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPLbRAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYxzZ6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz2EYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAe2+gBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxjGz0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHlsowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI9t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5rGNHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPLbRAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYxzZ6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz2EYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAe2+gBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxjGz0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHlsowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI9t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5rGNHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPLbRAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYxzZ6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz2EYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAe2+gBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxjGz0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHlsowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI9t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5rGNHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPLbRAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYxzZ6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz2EYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAe2+gBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxjGz0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHlsowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI9t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5rGNHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPLbRAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYxzZ6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz2EYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAe2+gBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxjGz0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHlsowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI9t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5rGNHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPLbRAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYxzZ6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz2EYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAe2+gBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxjGz0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHlsowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI9t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5rGNHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPLbRAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYxzZ6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz2EYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAe2+gBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxjGz0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHlsowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI9t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5rGNHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPLbRAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYxzZ6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz2EYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAe2+gBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxjGz0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHlsowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI9t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5rGNHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPLbRAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYxzZ6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz2EYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAe2+gBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxjGz0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHlsowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI9t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5rGNHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPLbRAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYxzZ6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz2EYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAe2+gBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMxjGz0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHlsowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI9t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5rGNHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPLbRAwAAAAAA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AAAAsBzT6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlmMaHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAckyjAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYjml0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADLMY0OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGA5ptEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACzHNDoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgOWYRgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsBzT6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlmMaHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAckyjAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYjml0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADLMY0OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGA5ptEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACzHNDoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgOWYRgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsBzT6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlmMaHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAckyjAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYjml0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADLMY0OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGA5ptEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACzHNDoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgOWYRgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsBzT6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlmMaHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAckyjAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYjml0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADLMY0OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGA5ptEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACzHNDoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgOWYRgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsBzT6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlmMaHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAckyjAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYjml0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADLMY0OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGA5ptEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACzHNDoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgOWYRgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/Dc79xf6/VnXcfx1vds/R0EnU9qycusgFQqig6KTpNFJEUFBjcCY4EE5RSoaWlFRlh2ZLVPKGoi1E62gLAjBhILCgw6KJOifm/dAh1iN1v60XR30E+79uO/NoPbmffF4wM33z+/6fD7PL6/zGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDmqOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOao7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5qjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAAAAAAAAAAAAAAAMI8+egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8+ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjz56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6KMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPPnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPoowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI8+egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8+ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjz56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6KMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPPnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPoowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI8+egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8+ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjz56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6KMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPPnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPoowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI8+egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8+ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjz56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6KMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPPnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPoowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI8+egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8+ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjz56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6KMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPPnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPoowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI8+egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8+ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjz56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6KMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPPnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPoowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI8+egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8+ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjz56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6KMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPPnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPoowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI8+egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8+ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjz56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6KMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPPnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPoowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI8+egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8+ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjz56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6KMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPPnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPoowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI8+egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8+ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjz56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6KMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPPnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPoowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI8+egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8+ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjz56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6KMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPPnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPoowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI8+egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8+ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjz56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6KMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPPnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPoowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI8+egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8+ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjz56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6KMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPPnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPoowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI8+egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8+ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjz56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6KMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPPnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPoowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI8+egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8+ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjz56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6KMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPPnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPoowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI8+egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8+ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjz56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6KMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPPnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPoowcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMI8+egAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8+ijBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwjz56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6KMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCPPnoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAgD5mdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfczqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6mNUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQxqwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6GNWBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQx6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCPWR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB+zOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPmZ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9zOoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPqY1QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9DGrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoY1YHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDHrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoI9ZHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH7M6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA+ZnUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYI7qDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgjuoOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCO6g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBHdQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEd1BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwR3UH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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObRRg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYB5t9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5tFGDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgHm30AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADm0UYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAebfQAAAAAAAAA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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMD/snN3IZ+ndR3HP9fVzO64KHiySrOZuuuBrqQUBUEdJC2dKCIY1CAou+BB7Wqp0bJWZIRrHaVtq1K6A4u1J+sD+AQiqAeC0YEHRhT0pOsMrYtstbSNs+5cHfQPZm/m3omo+fK9er1g+D/c1+/3e8/9Ob8BAAAA+pjVAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD0MasDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOhjVgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0MesDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgj1kdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAfszoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgD5mdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfczqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6mNUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQxqwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6GNWBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQx6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCPWR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB+zOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPmZ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9zOoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPqY1QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9DGrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoY1YHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDHrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoI9ZHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH7M6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA+ZnUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH3M6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+pjVAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD0MasDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOhjVgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0MesDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgj1kdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAfszoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgD5mdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfczqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6mNUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQxqwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6GNWBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQx6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCPWR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB+zOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPmZ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9zOoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPqY1QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9DGrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoY1YHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDHrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoI9ZHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH7M6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA+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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6mNUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQxqwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6GNWBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQx6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCPWR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB+zOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPmZ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9zOoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPqY1QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9DGrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoY1YHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDHrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoI9ZHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH7M6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA+ZnUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH3M6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAD6mNUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQxqwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6GNWBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQx6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCPWR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB+zOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPmZ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9zOoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPqY1QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9DGrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoY1YHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDHrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoI9ZHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH7M6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA+ZnUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH3M6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+pjVAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDz66AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKOPHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPProAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo48eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA8+ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDz66AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKOPHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPProAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo48eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA8+ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDz66AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKOPHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPProAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo48eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA8+ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDz66AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKOPHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPProAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo48eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA8+ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDz66AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKOPHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPProAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo48eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA8+ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDz66AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKOPHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPProAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo48eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA8+ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDz66AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKOPHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPProAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo48eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA8+ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDz66AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKOPHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPProAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo48eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA8+ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDz66AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKOPHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPProAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo48eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA8+ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDz66AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKOPHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPProAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo48eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA8+ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDz66AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKOPHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPProAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo48eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA8+ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDz66AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKOPHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPProAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo48eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA8+ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDz66AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKOPHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPProAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo48eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA8+ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDz66AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKOPHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPProAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo48eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA8+ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDz66AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKOPHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPProAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo48eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA8+ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDz66AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKOPHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPProAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo48eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA8+ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDz66AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKOPHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPProAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo48eAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAAAAAAAAAAAAAD6mNUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQxqwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6GNWBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQx6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCPWR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB+zOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPmZ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9zOoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPqY1QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9DGrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoY1YHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDHrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoI9ZHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH7M6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA+ZnUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH3M6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+pjVAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgD5mdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfczqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6mNUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQxqwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6GNWBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQx6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCPWR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB+zOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPmZ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9zOoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPqY1QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9DGrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoY1YHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDHrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoI9ZHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH7M6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA+ZnUAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgD5mdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfczqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6mNUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQxqwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6GNWBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQx6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCPWR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB+zOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPmZ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9zOoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPqY1QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9DGrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoY1YHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDHrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoI9ZHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH7M6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA+ZnUA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o40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPNroAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMo40eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA82ugBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMyjjR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDza6AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzKONHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAgD5mdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfczqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6mNUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQxqwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6GNWBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQx6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCPWR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB+zOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPmZ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9zOoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPqY1QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9DGrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoY1YHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDHrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoI9ZHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH7M6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA+ZnUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfczqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6mNUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQxqwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6GNWBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQx6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCPWR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB+zOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPmZ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9zOoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPqY1QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9DGrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoY1YHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDHrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoI9ZHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH7M6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA+ZnUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH3M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AAAAAAAAAAAAAAAAAAAAAAAAAABgcYyGDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA4hgNHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAxTEaOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAi2M0dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFsdo6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALI7R0AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWByjoQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsDhGQwcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYHGMhg4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwOIYDR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgMUxGjoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAItjNHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABbHaOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACyO0dABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgco6EDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALA4RkMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBxjIYOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDiGA0dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDFMRo6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACLYzR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWx2joAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsjtHQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYHKOhAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwOEZDBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgcYyGDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA4hgNHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAxTEaOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAi2M0dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFsdo6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALI7R0AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWByjoQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsDhGQwcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYHGMhg4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwOIYDR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgMUxGjoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAItjNHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABbHaOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACyO0dABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgco6EDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALA4RkMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBxjIYOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDiGA0dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDFMRo6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACLYzR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWx2joAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsjtHQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYHKOhAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwOEZDBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgcYyGDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA4hgNHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAxTEaOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAi2M0dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFsdo6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALI7R0AEAAAAAAAAAAAAAAAAAAP4/u3NAAgAAgAAI+j+6GRHoAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+JF1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAfWQcAAAAAAICyOwckAAAACICg/6ObEYEuEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4EfWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+ZB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICyOwckAAAACICg/6ObEYEuEAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4kXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB9ZBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACi7c0ACAACAAAj6P7oZEegCAQAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAADfC5TAAAOQrSURBVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5kHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgR9YBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgLJ3xzi2XUUUQPcr/cgyATNwQIITQobGODwsGAADICMnQnY/AicOiH5/q25trTWCXaqqc+69LfUDAAAAAAAAAAAAAAAAAAAAAADgjtkOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAdsx0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDtmOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd8x2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADumO0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANwx2wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuGO2AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwx2wHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOCO2Q4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwB2zHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAO2Y7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB3zHYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6Y7QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3DHbAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC4Y7YDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHDHbAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4I7ZDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAHbMdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA7ZjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHfMdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7pjtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcMdsBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALhjtgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcMdsBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgjtkOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAdsx0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDtmOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd8x2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADumO0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANwx2wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuGO2AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwx2wHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOCO2Q4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwB2zHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAO2Y7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB3zHYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6Y7QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3DHbAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC4Y7YDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHDHbAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4I7ZDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAHbMdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA7ZjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHfMdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7pjtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcMdsBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALhjtgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcMdsBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgjtkOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAdsx0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDtmOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd8x2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADumO0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANwx2wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuGO2AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwx2wHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOCO2Q4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwB2zHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAO2Y7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB3zHYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6Y7QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3DHbAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC4Y7YDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHDHbAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4I7ZDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAHbMdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA7ZjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHfMdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7pjtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcMdsBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALhjtgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcMdsBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgjtkOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAdsx0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDtmOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd8x2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADumO0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANwx2wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuGO2AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwx2wHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOCO2Q4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwB2zHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAO2Y7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB3zHYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6Y7QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3DHbAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC4Y7YDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHDHbAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4I7ZDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAHbMdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA7ZjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHfMdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7pjtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcMdsBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALhjtgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcMdsBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgjtkOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAdsx0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDtmOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd8x2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADumO0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANwx2wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuGO2AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwx2wHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOCO2Q4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwB2zHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAO2Y7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB3zHYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6Y7QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3DHbAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC4Y7YDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHDHbAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4I7ZDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAHbMdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA7ZjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHfMdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7pjtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcMdsBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALhjtgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcMdsBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgjtkOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAdsx0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgDtmOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd8x2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADumO0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANwx2wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuGO2AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwx2wHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOCO2Q4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwB2zHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAO2Y7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB3zHYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6Y7QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3DHbAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC4Y7YDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHDHbAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4I7ZDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAHbMdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA7ZjsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHfMdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7pjtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcMdsBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB1pBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwI+0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHakHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAjrQDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANiRdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO9IOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBH2gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7Eg7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAdaQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsCPtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2pB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwI60AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADYkXYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADvSDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgR9oBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxIOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAHWkHAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAAAAAAA2JF2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA70g4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYEfaAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsSDsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB1pBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwI+0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHakHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAjrQDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANiRdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO9IOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBH2gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7Eg7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAdaQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsCPtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2pB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwI60AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADYkXYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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SDsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB1pBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwI+0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHakHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAjrQDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANiRdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO9IOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBH2gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7Eg7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAdaQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsCPtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2pB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwI60AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADYkXYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADvSDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgR9oBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxIOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAHWkHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAj7QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdqQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCOtAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2JF2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwy+4ckAAAACAAgv6PbkYEukAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPiRdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAH1kHAAAAAAAAAAAAAAAAAAAAAACg7M4BCQAAAAIg6P/oZkSgCwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfmQdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBH1gEAAAAAAAAAAADK3t2F2npdZQB+xyS1IkoubC5EoYEWay2WKgVTQawoFaNVqKA3Kv5VIdjSKFWKoCnWgpVUbRBqL1T0KtKaoFWQiqaaGhoMGhpjwUBTazRIRINJa7XJ9GLvyOawz0/OPu655vB5IJzDOt/61jsyf7611mDxAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA+xuoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxjrA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwD7G6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7GOsDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAPsbqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsY6wOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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\n", + "text/plain": [ + "<Figure size 16384x8192 with 2 Axes>" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# Plot of array values\n", + "\n", + "heatmap = np.array(array_values,dtype=np.float)\n", + "fig = plt.figure()\n", + "plt.rcParams['figure.figsize'] = [128, 64]\n", + "plt.rcParams['figure.dpi'] = 128\n", + "ax = fig.add_subplot(111)\n", + "im = ax.imshow(heatmap, interpolation='nearest',cmap='coolwarm')\n", + "ax.set_xlabel('Array index')\n", + "ax.set_ylabel('Timestamp')\n", + "ax.set_xlim([0,(records[0].dim_x_r)-1])\n", + "ax.set_xticks(np.arange(0,records[0].dim_x_r))\n", + "\n", + "ax.set_yticks(range(0,len(timestamps)))\n", + "ax.set_yticklabels(timestamps,fontsize=4)\n", + "\n", + "# Comment the previous two lines and uncomment the following line if there are too many timestamp labels\n", + "#ax.set_yticks(range(0,len(timestamps),10))\n", + "\n", + "ax.set_title('Archived data for '+ attr_fq_name)\n", + "ax.grid()\n", + "cbar = fig.colorbar(ax=ax, mappable=im, orientation='horizontal')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "95fb14a2", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "StationControl", + "language": "python", + "name": "stationcontrol" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/jupyter-notebooks/RECV_notebook.ipynb b/jupyter-notebooks/RECV_notebook.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..6892f75ca7a91ef3e5bed866c0e3a6c3e9f2d1bb --- /dev/null +++ b/jupyter-notebooks/RECV_notebook.ipynb @@ -0,0 +1,194 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "waiting-chance", + "metadata": {}, + "outputs": [], + "source": [ + "import time" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "moving-alexandria", + "metadata": {}, + "outputs": [], + "source": [ + "d=DeviceProxy(\"STAT/RECV/1\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "ranking-aluminum", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Device is now in on state\n" + ] + } + ], + "source": [ + "state = str(d.state())\n", + "\n", + "\n", + "if state == \"OFF\" or state == \"FAULT\":\n", + " d.initialise()\n", + " time.sleep(1)\n", + "state = str(d.state())\n", + "if state == \"STANDBY\":\n", + " d.on()\n", + "state = str(d.state())\n", + "if state == \"ON\":\n", + " print(\"Device is now in on state\")\n", + "else:\n", + " print(\"warning, expected device to be in on state, is: \", state)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "transsexual-battle", + "metadata": {}, + "outputs": [ + { + "ename": "DevFailed", + "evalue": "DevFailed[\nDevError[\n desc = Read value for attribute FPGA_mask_RW has not been updated\n origin = Device_3Impl::read_attributes_no_except\n reason = API_AttrValueNotSet\nseverity = ERR]\n\nDevError[\n desc = Failed to read_attribute on device lts/sdp/1, attribute FPGA_mask_RW\n origin = DeviceProxy::read_attribute()\n reason = API_AttributeFailed\nseverity = ERR]\n]", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mDevFailed\u001B[0m Traceback (most recent call last)", + "\u001B[0;32m/tmp/ipykernel_22/2885399456.py\u001B[0m in \u001B[0;36m<module>\u001B[0;34m\u001B[0m\n\u001B[1;32m 1\u001B[0m values = [\n\u001B[0;32m----> 2\u001B[0;31m \u001B[0;34m[\u001B[0m\u001B[0md\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mFPGA_mask_RW\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m\"FPGA_mask_RW\"\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 3\u001B[0m \u001B[0;34m[\u001B[0m\u001B[0md\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mFPGA_scrap_R\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m\"FPGA_scrap_R\"\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 4\u001B[0m \u001B[0;34m[\u001B[0m\u001B[0md\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mFPGA_scrap_RW\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m\"FPGA_scrap_RW\"\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 5\u001B[0m \u001B[0;34m[\u001B[0m\u001B[0md\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mFPGA_status_R\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m\"FPGA_status_R\"\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001B[0m in \u001B[0;36m__DeviceProxy__getattr\u001B[0;34m(self, name)\u001B[0m\n\u001B[1;32m 319\u001B[0m \u001B[0mattr_info\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m__get_attr_cache\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mget\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mname_l\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 320\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mattr_info\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 321\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0m__get_attribute_value\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mattr_info\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mname\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 322\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 323\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mname_l\u001B[0m \u001B[0;32min\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m__get_pipe_cache\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001B[0m in \u001B[0;36m__get_attribute_value\u001B[0;34m(self, attr_info, name)\u001B[0m\n\u001B[1;32m 281\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0m__get_attribute_value\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mattr_info\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mname\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 282\u001B[0m \u001B[0m_\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0menum_class\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mattr_info\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 283\u001B[0;31m \u001B[0mattr_value\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mread_attribute\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mname\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mvalue\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 284\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0menum_class\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 285\u001B[0m \u001B[0;32mreturn\u001B[0m \u001B[0menum_class\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mattr_value\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/usr/local/lib/python3.7/dist-packages/tango/green.py\u001B[0m in \u001B[0;36mgreener\u001B[0;34m(obj, *args, **kwargs)\u001B[0m\n\u001B[1;32m 193\u001B[0m \u001B[0mgreen_mode\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0maccess\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'green_mode'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 194\u001B[0m \u001B[0mexecutor\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mget_object_executor\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mobj\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mgreen_mode\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 195\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mexecutor\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrun\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mfn\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0margs\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mkwargs\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mwait\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mwait\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mtimeout\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mtimeout\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 196\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 197\u001B[0m \u001B[0;32mreturn\u001B[0m \u001B[0mgreener\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/usr/local/lib/python3.7/dist-packages/tango/green.py\u001B[0m in \u001B[0;36mrun\u001B[0;34m(self, fn, args, kwargs, wait, timeout)\u001B[0m\n\u001B[1;32m 107\u001B[0m \u001B[0;31m# Sychronous (no delegation)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 108\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0;32mnot\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0masynchronous\u001B[0m \u001B[0;32mor\u001B[0m \u001B[0;32mnot\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0min_executor_context\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 109\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mfn\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m*\u001B[0m\u001B[0margs\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m**\u001B[0m\u001B[0mkwargs\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 110\u001B[0m \u001B[0;31m# Asynchronous delegation\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 111\u001B[0m \u001B[0maccessor\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mdelegate\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mfn\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m*\u001B[0m\u001B[0margs\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m**\u001B[0m\u001B[0mkwargs\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001B[0m in \u001B[0;36m__DeviceProxy__read_attribute\u001B[0;34m(self, value, extract_as)\u001B[0m\n\u001B[1;32m 439\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 440\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0m__DeviceProxy__read_attribute\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mvalue\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mextract_as\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mExtractAs\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mNumpy\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 441\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0m__check_read_attribute\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_read_attribute\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mvalue\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mextract_as\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 442\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 443\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001B[0m in \u001B[0;36m__check_read_attribute\u001B[0;34m(dev_attr)\u001B[0m\n\u001B[1;32m 155\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0m__check_read_attribute\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdev_attr\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 156\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mdev_attr\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mhas_failed\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 157\u001B[0;31m \u001B[0;32mraise\u001B[0m \u001B[0mDevFailed\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m*\u001B[0m\u001B[0mdev_attr\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mget_err_stack\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 158\u001B[0m \u001B[0;32mreturn\u001B[0m \u001B[0mdev_attr\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 159\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;31mDevFailed\u001B[0m: DevFailed[\nDevError[\n desc = Read value for attribute FPGA_mask_RW has not been updated\n origin = Device_3Impl::read_attributes_no_except\n reason = API_AttrValueNotSet\nseverity = ERR]\n\nDevError[\n desc = Failed to read_attribute on device lts/sdp/1, attribute FPGA_mask_RW\n origin = DeviceProxy::read_attribute()\n reason = API_AttributeFailed\nseverity = ERR]\n]" + ] + } + ], + "source": [ + "values = [\n", + " [d.FPGA_mask_RW, \"FPGA_mask_RW\"],\n", + " [d.FPGA_scrap_R, \"FPGA_scrap_R\"],\n", + " [d.FPGA_scrap_RW, \"FPGA_scrap_RW\"],\n", + " [d.FPGA_status_R, \"FPGA_status_R\"],\n", + " [d.FPGA_temp_R, \"FPGA_temp_R\"],\n", + " [d.FPGA_version_R, \"FPGA_version_R\"],\n", + " [d.FPGA_weights_R, \"FPGA_weights_R\"],\n", + " [d.FPGA_weights_RW, \"FPGA_weights_RW\"],\n", + " [d.TR_busy_R, \"TR_busy_R\"],\n", + " [d.TR_reload_RW, \"TR_reload_RW\"],\n", + " # [d.TR_tod_R, \"TR_tod_R\"],\n", + " # [d.TR_uptime_R, \"TR_uptime_R\"]\n", + "]\n", + "\n", + "for i in values:\n", + " print(\"🟦🟦🟦\", i[1], \": \", i[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "7accae6a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "version_R *L2SS-357-Rename_PCC_to_RECV [c4d52d7125ece480acb1492a5fc0ba7fc60f9ea1]\n", + "Ant_mask_RW [[False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]\n", + " [False False False]]\n" + ] + }, + { + "ename": "DevFailed", + "evalue": "DevFailed[\nDevError[\n desc = Read value for attribute CLK_Enable_PWR_R has not been updated\n origin = Device_3Impl::read_attributes_no_except\n reason = API_AttrValueNotSet\nseverity = ERR]\n\nDevError[\n desc = Failed to read_attribute on device lts/recv/1, attribute CLK_Enable_PWR_R\n origin = DeviceProxy::read_attribute()\n reason = API_AttributeFailed\nseverity = ERR]\n]", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mDevFailed\u001B[0m Traceback (most recent call last)", + "\u001B[0;32m/tmp/ipykernel_26/3093379163.py\u001B[0m in \u001B[0;36m<module>\u001B[0;34m\u001B[0m\n\u001B[1;32m 3\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 4\u001B[0m \u001B[0;32mfor\u001B[0m \u001B[0mi\u001B[0m \u001B[0;32min\u001B[0m \u001B[0mattr_names\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m----> 5\u001B[0;31m \u001B[0mexec\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m\"value = print(i, d.{})\"\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mformat\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mi\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m", + "\u001B[0;32m<string>\u001B[0m in \u001B[0;36m<module>\u001B[0;34m\u001B[0m\n", + "\u001B[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001B[0m in \u001B[0;36m__DeviceProxy__getattr\u001B[0;34m(self, name)\u001B[0m\n\u001B[1;32m 319\u001B[0m \u001B[0mattr_info\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m__get_attr_cache\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mget\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mname_l\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 320\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mattr_info\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 321\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0m__get_attribute_value\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mattr_info\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mname\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 322\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 323\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mname_l\u001B[0m \u001B[0;32min\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m__get_pipe_cache\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001B[0m in \u001B[0;36m__get_attribute_value\u001B[0;34m(self, attr_info, name)\u001B[0m\n\u001B[1;32m 281\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0m__get_attribute_value\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mattr_info\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mname\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 282\u001B[0m \u001B[0m_\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0menum_class\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mattr_info\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 283\u001B[0;31m \u001B[0mattr_value\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mread_attribute\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mname\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mvalue\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 284\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0menum_class\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 285\u001B[0m \u001B[0;32mreturn\u001B[0m \u001B[0menum_class\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mattr_value\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/usr/local/lib/python3.7/dist-packages/tango/green.py\u001B[0m in \u001B[0;36mgreener\u001B[0;34m(obj, *args, **kwargs)\u001B[0m\n\u001B[1;32m 193\u001B[0m \u001B[0mgreen_mode\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0maccess\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'green_mode'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 194\u001B[0m \u001B[0mexecutor\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mget_object_executor\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mobj\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mgreen_mode\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 195\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mexecutor\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrun\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mfn\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0margs\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mkwargs\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mwait\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mwait\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mtimeout\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mtimeout\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 196\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 197\u001B[0m \u001B[0;32mreturn\u001B[0m \u001B[0mgreener\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/usr/local/lib/python3.7/dist-packages/tango/green.py\u001B[0m in \u001B[0;36mrun\u001B[0;34m(self, fn, args, kwargs, wait, timeout)\u001B[0m\n\u001B[1;32m 107\u001B[0m \u001B[0;31m# Sychronous (no delegation)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 108\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0;32mnot\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0masynchronous\u001B[0m \u001B[0;32mor\u001B[0m \u001B[0;32mnot\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0min_executor_context\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 109\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mfn\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m*\u001B[0m\u001B[0margs\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m**\u001B[0m\u001B[0mkwargs\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 110\u001B[0m \u001B[0;31m# Asynchronous delegation\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 111\u001B[0m \u001B[0maccessor\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mdelegate\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mfn\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m*\u001B[0m\u001B[0margs\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m**\u001B[0m\u001B[0mkwargs\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001B[0m in \u001B[0;36m__DeviceProxy__read_attribute\u001B[0;34m(self, value, extract_as)\u001B[0m\n\u001B[1;32m 439\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 440\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0m__DeviceProxy__read_attribute\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mvalue\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mextract_as\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mExtractAs\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mNumpy\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 441\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0m__check_read_attribute\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_read_attribute\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mvalue\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mextract_as\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 442\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 443\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001B[0m in \u001B[0;36m__check_read_attribute\u001B[0;34m(dev_attr)\u001B[0m\n\u001B[1;32m 155\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0m__check_read_attribute\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdev_attr\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 156\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mdev_attr\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mhas_failed\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 157\u001B[0;31m \u001B[0;32mraise\u001B[0m \u001B[0mDevFailed\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m*\u001B[0m\u001B[0mdev_attr\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mget_err_stack\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 158\u001B[0m \u001B[0;32mreturn\u001B[0m \u001B[0mdev_attr\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 159\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;31mDevFailed\u001B[0m: DevFailed[\nDevError[\n desc = Read value for attribute CLK_Enable_PWR_R has not been updated\n origin = Device_3Impl::read_attributes_no_except\n reason = API_AttrValueNotSet\nseverity = ERR]\n\nDevError[\n desc = Failed to read_attribute on device lts/recv/1, attribute CLK_Enable_PWR_R\n origin = DeviceProxy::read_attribute()\n reason = API_AttributeFailed\nseverity = ERR]\n]" + ] + } + ], + "source": [ + "attr_names = d.get_attribute_list()\n", + "\n", + "\n", + "for i in attr_names:\n", + " exec(\"value = print(i, d.{})\".format(i))\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "StationControl", + "language": "python", + "name": "stationcontrol" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/jupyter-notebooks/SDP_notebook.ipynb b/jupyter-notebooks/SDP_notebook.ipynb deleted file mode 100644 index 69ae185b050d935bdd5e49bb429a533da1510927..0000000000000000000000000000000000000000 --- a/jupyter-notebooks/SDP_notebook.ipynb +++ /dev/null @@ -1,132 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 6, - "id": "waiting-chance", - "metadata": {}, - "outputs": [], - "source": [ - "import time" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "moving-alexandria", - "metadata": {}, - "outputs": [], - "source": [ - "d=DeviceProxy(\"LTS/SDP/1\")" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "ranking-aluminum", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "warning, expected device to be in on state, is: FAULT\n" - ] - } - ], - "source": [ - "state = str(d.state())\n", - "\n", - "if state == \"OFF\":\n", - " d.initialise()\n", - " time.sleep(1)\n", - "state = str(d.state())\n", - "if state == \"STANDBY\":\n", - " d.on()\n", - "state = str(d.state())\n", - "if state == \"ON\":\n", - " print(\"Device is now in on state\")\n", - "else:\n", - " print(\"warning, expected device to be in on state, is: \", state)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "transsexual-battle", - "metadata": {}, - "outputs": [], - "source": [ - "values = [\n", - " [d.FPGA_mask_RW, \"FPGA_mask_RW\"],\n", - " [d.FPGA_scrap_R, \"FPGA_scrap_R\"],\n", - " [d.FPGA_scrap_RW, \"FPGA_scrap_RW\"],\n", - " [d.FPGA_status_R, \"FPGA_status_R\"],\n", - " [d.FPGA_temp_R, \"FPGA_temp_R\"],\n", - " [d.FPGA_version_R, \"FPGA_version_R\"],\n", - " [d.FPGA_weights_R, \"FPGA_weights_R\"],\n", - " [d.FPGA_weights_RW, \"FPGA_weights_RW\"],\n", - " [d.TR_busy_R, \"TR_busy_R\"],\n", - " [d.TR_reload_RW, \"TR_reload_RW\"],\n", - " # [d.TR_tod_R, \"TR_tod_R\"],\n", - " # [d.TR_uptime_R, \"TR_uptime_R\"]\n", - "]\n", - "\n", - "for i in values:\n", - " print(\"🟦🟦🟦\", i[1], \": \", i[0])" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "eligible-times", - "metadata": {}, - "outputs": [ - { - "ename": "DevFailed", - "evalue": "DevFailed[\nDevError[\n desc = TypeError: Expecting a numeric type, but it is not. If you use a numpy type instead of python core types, then it must exactly match (ex: numpy.int32 for PyTango.DevLong)\n \n origin = Traceback (most recent call last):\n File \"/usr/local/lib/python3.7/dist-packages/tango/server.py\", line 138, in read_attr\n set_complex_value(attr, ret)\n File \"/usr/local/lib/python3.7/dist-packages/tango/server.py\", line 115, in set_complex_value\n attr.set_value(value)\nTypeError: Expecting a numeric type, but it is not. If you use a numpy type instead of python core types, then it must exactly match (ex: numpy.int32 for PyTango.DevLong)\n\n reason = PyDs_PythonError\nseverity = ERR]\n\nDevError[\n desc = Failed to read_attribute on device lts/sdp/1, attribute TR_tod_R\n origin = DeviceProxy::read_attribute()\n reason = API_AttributeFailed\nseverity = ERR]\n]", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mDevFailed\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m<ipython-input-5-e44d5c52394a>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mTR_tod_R\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__DeviceProxy__getattr\u001b[0;34m(self, name)\u001b[0m\n\u001b[1;32m 319\u001b[0m \u001b[0mattr_info\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__get_attr_cache\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname_l\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 320\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mattr_info\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 321\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m__get_attribute_value\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mattr_info\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 322\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 323\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mname_l\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__get_pipe_cache\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__get_attribute_value\u001b[0;34m(self, attr_info, name)\u001b[0m\n\u001b[1;32m 281\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__get_attribute_value\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mattr_info\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 282\u001b[0m \u001b[0m_\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0menum_class\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mattr_info\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 283\u001b[0;31m \u001b[0mattr_value\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 284\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0menum_class\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 285\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0menum_class\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mattr_value\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/green.py\u001b[0m in \u001b[0;36mgreener\u001b[0;34m(obj, *args, **kwargs)\u001b[0m\n\u001b[1;32m 193\u001b[0m \u001b[0mgreen_mode\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0maccess\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'green_mode'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 194\u001b[0m \u001b[0mexecutor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_object_executor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgreen_mode\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 195\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mexecutor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwait\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mwait\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimeout\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mtimeout\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 196\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 197\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mgreener\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/green.py\u001b[0m in \u001b[0;36mrun\u001b[0;34m(self, fn, args, kwargs, wait, timeout)\u001b[0m\n\u001b[1;32m 107\u001b[0m \u001b[0;31m# Sychronous (no delegation)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 108\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0masynchronous\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0min_executor_context\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 109\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 110\u001b[0m \u001b[0;31m# Asynchronous delegation\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 111\u001b[0m \u001b[0maccessor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdelegate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__DeviceProxy__read_attribute\u001b[0;34m(self, value, extract_as)\u001b[0m\n\u001b[1;32m 439\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 440\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__DeviceProxy__read_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mextract_as\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mExtractAs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mNumpy\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 441\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m__check_read_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_read_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mextract_as\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 442\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 443\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__check_read_attribute\u001b[0;34m(dev_attr)\u001b[0m\n\u001b[1;32m 155\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__check_read_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdev_attr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 156\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mdev_attr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mhas_failed\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 157\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mDevFailed\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mdev_attr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_err_stack\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 158\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mdev_attr\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 159\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mDevFailed\u001b[0m: DevFailed[\nDevError[\n desc = TypeError: Expecting a numeric type, but it is not. If you use a numpy type instead of python core types, then it must exactly match (ex: numpy.int32 for PyTango.DevLong)\n \n origin = Traceback (most recent call last):\n File \"/usr/local/lib/python3.7/dist-packages/tango/server.py\", line 138, in read_attr\n set_complex_value(attr, ret)\n File \"/usr/local/lib/python3.7/dist-packages/tango/server.py\", line 115, in set_complex_value\n attr.set_value(value)\nTypeError: Expecting a numeric type, but it is not. If you use a numpy type instead of python core types, then it must exactly match (ex: numpy.int32 for PyTango.DevLong)\n\n reason = PyDs_PythonError\nseverity = ERR]\n\nDevError[\n desc = Failed to read_attribute on device lts/sdp/1, attribute TR_tod_R\n origin = DeviceProxy::read_attribute()\n reason = API_AttributeFailed\nseverity = ERR]\n]" - ] - } - ], - "source": [ - "attr_names = d.get_attribute_list()\n", - "\n", - "for i in attr_names:\n", - " exec(\"value = print(i, d.{})\".format(i))\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "StationControl", - "language": "python", - "name": "stationcontrol" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/jupyter-notebooks/Start All Devices.ipynb b/jupyter-notebooks/Start All Devices.ipynb index beb52a381c89a4cda30b08374d36c337def29eae..3c5da68df6ce970a837e83903379f88435cc1483 100644 --- a/jupyter-notebooks/Start All Devices.ipynb +++ b/jupyter-notebooks/Start All Devices.ipynb @@ -30,7 +30,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Device PCC(lts/pcc/1) is now in state FAULT\n", + "Device RECV(lts/recv/1) is now in state FAULT\n", "Device SDP(lts/sdp/1) is now in state ON\n" ] } diff --git a/jupyter-notebooks/UNB2_notebook.ipynb b/jupyter-notebooks/UNB2_notebook.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..d3fcdc6794d746d2168f661ec4f3fc08f9550ebe --- /dev/null +++ b/jupyter-notebooks/UNB2_notebook.ipynb @@ -0,0 +1,479 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 25, + "id": "waiting-chance", + "metadata": {}, + "outputs": [], + "source": [ + "import time" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "moving-alexandria", + "metadata": {}, + "outputs": [], + "source": [ + "d=DeviceProxy(\"STAT/UNB2/1\")" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "ranking-aluminum", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Device is now in on state\n" + ] + } + ], + "source": [ + "state = str(d.state())\n", + "\n", + "\n", + "if state == \"OFF\" or state == \"FAULT\":\n", + " d.initialise()\n", + " time.sleep(1)\n", + "state = str(d.state())\n", + "if state == \"STANDBY\":\n", + " d.on()\n", + "state = str(d.state())\n", + "if state == \"ON\":\n", + " print(\"Device is now in on state\")\n", + "else:\n", + " print(\"warning, expected device to be in on state, is: \", state)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "0caa8146", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "version_R *L2SS-268-LR1_2_Read_hardware_status_of_UB2c_from_SDPHW [b2db449162be8e52013dbbd1a44d6d90a12491b5]\n", + "UNB2_Power_ON_OFF_RW [False False]\n", + "UNB2_Front_Panel_LED_RW [0 0]\n", + "UNB2_Front_Panel_LED_R [0 0]\n", + "UNB2_mask_RW [False False]\n", + "UNB2_I2C_bus_STATUS_R [False False]\n", + "UNB2_EEPROM_Unique_ID_R [0 0]\n", + "UNB2_DC_DC_48V_12V_VIN_R [0. 0.]\n", + "UNB2_DC_DC_48V_12V_VOUT_R [0. 0.]\n", + "UNB2_DC_DC_48V_12V_IOUT_R [0. 0.]\n", + "UNB2_DC_DC_48V_12V_TEMP_R [0. 0.]\n", + "UNB2_POL_QSFP_N01_VOUT_R [0. 0.]\n", + "UNB2_POL_QSFP_N01_IOUT_R [0. 0.]\n", + "UNB2_POL_QSFP_N01_TEMP_R [0. 0.]\n", + "UNB2_POL_QSFP_N23_VOUT_R [0. 0.]\n", + "UNB2_POL_QSFP_N23_IOUT_R [0. 0.]\n", + "UNB2_POL_QSFP_N23_TEMP_R [0. 0.]\n", + "UNB2_POL_SWITCH_1V2_VOUT_R [0. 0.]\n", + "UNB2_POL_SWITCH_1V2_IOUT_R [0. 0.]\n", + "UNB2_POL_SWITCH_1V2_TEMP_R [0. 0.]\n", + "UNB2_POL_SWITCH_PHY_VOUT_R [0. 0.]\n", + "UNB2_POL_SWITCH_PHY_IOUT_R [0. 0.]\n", + "UNB2_POL_SWITCH_PHY_TEMP_R [0. 0.]\n", + "UNB2_POL_CLOCK_VOUT_R [0. 0.]\n", + "UNB2_POL_CLOCK_IOUT_R [0. 0.]\n", + "UNB2_POL_CLOCK_TEMP_R [0. 0.]\n", + "UNB2_FPGA_DDR4_SLOT_TEMP_R [[0. 0. 0. 0. 0. 0. 0. 0.]\n", + " [0. 0. 0. 0. 0. 0. 0. 0.]]\n", + "UNB2_FPGA_POL_CORE_IOUT_R [[0. 0. 0. 0.]\n", + " [0. 0. 0. 0.]]\n", + "UNB2_FPGA_POL_CORE_TEMP_R [[0. 0. 0. 0.]\n", + " [0. 0. 0. 0.]]\n", + "UNB2_FPGA_POL_ERAM_VOUT_R [[0. 0. 0. 0.]\n", + " [0. 0. 0. 0.]]\n", + "UNB2_FPGA_POL_ERAM_IOUT_R [[0. 0. 0. 0.]\n", + " [0. 0. 0. 0.]]\n", + "UNB2_FPGA_POL_ERAM_TEMP_R [[0. 0. 0. 0.]\n", + " [0. 0. 0. 0.]]\n", + "UNB2_FPGA_POL_RXGXB_VOUT_R [[0. 0. 0. 0.]\n", + " [0. 0. 0. 0.]]\n", + "UNB2_FPGA_POL_RXGXB_IOUT_R [[0. 0. 0. 0.]\n", + " [0. 0. 0. 0.]]\n", + "UNB2_FPGA_POL_RXGXB_TEMP_R [[0. 0. 0. 0.]\n", + " [0. 0. 0. 0.]]\n", + "UNB2_FPGA_POL_TXGXB_VOUT_R [[0. 0. 0. 0.]\n", + " [0. 0. 0. 0.]]\n", + "UNB2_FPGA_POL_TXGXB_IOUT_R [[0. 0. 0. 0.]\n", + " [0. 0. 0. 0.]]\n", + "UNB2_FPGA_POL_HGXB_VOUT_R [[0. 0. 0. 0.]\n", + " [0. 0. 0. 0.]]\n", + "UNB2_FPGA_POL_HGXB_IOUT_R [[0. 0. 0. 0.]\n", + " [0. 0. 0. 0.]]\n", + "UNB2_FPGA_POL_HGXB_TEMP_R [[0. 0. 0. 0.]\n", + " [0. 0. 0. 0.]]\n", + "UNB2_FPGA_POL_PGM_VOUT_R [[0. 0. 0. 0.]\n", + " [0. 0. 0. 0.]]\n", + "UNB2_FPGA_POL_PGM_IOUT_R [[0. 0. 0. 0.]\n", + " [0. 0. 0. 0.]]\n", + "UNB2_FPGA_POL_PGM_TEMP_R [[0. 0. 0. 0.]\n", + " [0. 0. 0. 0.]]\n", + "State <function __get_command_func.<locals>.f at 0x7f4c210e1ea0>\n", + "Status <function __get_command_func.<locals>.f at 0x7f4c210e1ea0>\n" + ] + } + ], + "source": [ + "attr_names = d.get_attribute_list()\n", + "\n", + "\n", + "for i in attr_names:\n", + " exec(\"value = print(i, d.{})\".format(i))" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "929965c2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Old values:\n", + " [0 0]\n", + "Values to be set:\n", + " [1 3]\n", + "Values set in RW:\n", + " [1 3]\n", + "Values read back after setting:\n", + " [0 0]\n" + ] + } + ], + "source": [ + "#Test the LED CP\n", + "led = d.UNB2_Front_Panel_LED_R\n", + "print(\"Old values:\\n\", led)\n", + "led[0] = 1\n", + "led[1] = 3\n", + "print(\"Values to be set:\\n\", led)\n", + "d.UNB2_Front_Panel_LED_RW = led\n", + "print(\"Values set in RW:\\n\",d.UNB2_Front_Panel_LED_RW)\n", + "print(\"Values read back after setting:\\n\",d.UNB2_Front_Panel_LED_R)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "6813164e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Old values:\n", + " [False False]\n", + "Values to be set:\n", + " [False False]\n", + "Values set in RW:\n", + " [False False]\n" + ] + } + ], + "source": [ + "#Test the ON OFF CP\n", + "onoff = d.UNB2_Power_ON_OFF_RW\n", + "print(\"Old values:\\n\", onoff)\n", + "onoff[0] = False\n", + "onoff[1] = False\n", + "print(\"Values to be set:\\n\", onoff)\n", + "d.UNB2_Power_ON_OFF_RW = onoff\n", + "print(\"Values set in RW:\\n\",d.UNB2_Power_ON_OFF_RW)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "e9b32ec7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Old values:\n", + " [False False]\n", + "Values to be set:\n", + " [False False]\n", + "Values read back after setting:\n", + " [False False]\n" + ] + } + ], + "source": [ + "#Test the MASK CP\n", + "mask = d.UNB2_mask_RW\n", + "print(\"Old values:\\n\", mask)\n", + "mask[0] = False\n", + "mask[1] = False\n", + "print(\"Values to be set:\\n\", mask)\n", + "d.UNB2_mask_RW = mask\n", + "print(\"Values read back after setting:\\n\",d.UNB2_mask_RW)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "transsexual-battle", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "FPGA_mask_RW", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mAttributeError\u001B[0m Traceback (most recent call last)", + "\u001B[0;32m/tmp/ipykernel_22/2885399456.py\u001B[0m in \u001B[0;36m<module>\u001B[0;34m\u001B[0m\n\u001B[1;32m 1\u001B[0m values = [\n\u001B[0;32m----> 2\u001B[0;31m \u001B[0;34m[\u001B[0m\u001B[0md\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mFPGA_mask_RW\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m\"FPGA_mask_RW\"\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 3\u001B[0m \u001B[0;34m[\u001B[0m\u001B[0md\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mFPGA_scrap_R\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m\"FPGA_scrap_R\"\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 4\u001B[0m \u001B[0;34m[\u001B[0m\u001B[0md\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mFPGA_scrap_RW\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m\"FPGA_scrap_RW\"\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 5\u001B[0m \u001B[0;34m[\u001B[0m\u001B[0md\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mFPGA_status_R\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m\"FPGA_status_R\"\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001B[0m in \u001B[0;36m__DeviceProxy__getattr\u001B[0;34m(self, name)\u001B[0m\n\u001B[1;32m 353\u001B[0m \u001B[0;32mreturn\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mread_pipe\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mname\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 354\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 355\u001B[0;31m \u001B[0msix\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mraise_from\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mAttributeError\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mname\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mcause\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 356\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 357\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/usr/local/lib/python3.7/dist-packages/six.py\u001B[0m in \u001B[0;36mraise_from\u001B[0;34m(value, from_value)\u001B[0m\n", + "\u001B[0;31mAttributeError\u001B[0m: FPGA_mask_RW" + ] + } + ], + "source": [ + "values = [\n", + " [d.FPGA_mask_RW, \"FPGA_mask_RW\"],\n", + " [d.FPGA_scrap_R, \"FPGA_scrap_R\"],\n", + " [d.FPGA_scrap_RW, \"FPGA_scrap_RW\"],\n", + " [d.FPGA_status_R, \"FPGA_status_R\"],\n", + " [d.FPGA_temp_R, \"FPGA_temp_R\"],\n", + " [d.FPGA_version_R, \"FPGA_version_R\"],\n", + " [d.FPGA_weights_R, \"FPGA_weights_R\"],\n", + " [d.FPGA_weights_RW, \"FPGA_weights_RW\"],\n", + " [d.TR_busy_R, \"TR_busy_R\"],\n", + " [d.TR_reload_RW, \"TR_reload_RW\"],\n", + " # [d.TR_tod_R, \"TR_tod_R\"],\n", + " # [d.TR_uptime_R, \"TR_uptime_R\"]\n", + "]\n", + "\n", + "for i in values:\n", + " print(\"🟦🟦🟦\", i[1], \": \", i[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "b88868c5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", + " [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", + " [1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]],\n", + " dtype=float32)" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "wgswitches = d.FPGA_wg_enable_R\n", + "print(\"Old values:\\n\", wgswitches)\n", + "wgswitches[9][0] = True\n", + "wgswitches[10][0] = True\n", + "print(\"Values to be set:\\n\", wgswitches)\n", + "d.FPGA_wg_enable_RW =wgswitches\n", + "time.sleep(7)\n", + "print(\"Values read back after setting:\\n\",d.FPGA_wg_enable_R)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "8f3db8c7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[119.99817, 119.99817, 119.99817, 119.99817, 119.99817, 119.99817,\n", + " 119.99817, 119.99817, 119.99817, 119.99817, 119.99817, 119.99817,\n", + " 119.99817, 119.99817, 119.99817, 119.99817],\n", + " [119.99817, 119.99817, 119.99817, 119.99817, 119.99817, 119.99817,\n", + " 119.99817, 119.99817, 119.99817, 119.99817, 119.99817, 119.99817,\n", + " 119.99817, 119.99817, 119.99817, 119.99817],\n", + " [119.99817, 119.99817, 119.99817, 119.99817, 0. , 0. ,\n", + " 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. ],\n", + " [ 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. ],\n", + " [ 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. ],\n", + " [ 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. ],\n", + " [ 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. ],\n", + " [ 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. ],\n", + " [ 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. ],\n", + " [ 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. ],\n", + " [ 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. ],\n", + " [ 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. , 0. , 0. ,\n", + " 0. , 0. , 0. , 0. ]], dtype=float32)" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "phases = d.FPGA_wg_phase_R\n", + "print(\"Old values:\\n\", phases)\n", + "phases[9][0] = 1.0334\n", + "phases[9][1] = 20.15\n", + "phases[10][0] = 130\n", + "print(\"Values to be set:\\n\", phases)\n", + "d.FPGA_wg_phase_RW = phases\n", + "time.sleep(7)\n", + "print(\"Values read back after setting:\\n\", d.FPGA_wg_phase_R)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "e45b4874", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[29921878., 29921878., 29921878., 29921878., 29921878., 29921878.,\n", + " 29921878., 29921878., 29921878., 29921878., 29921878., 29921878.,\n", + " 29921878., 29921878., 29921878., 29921878.],\n", + " [29921878., 29921878., 29921878., 29921878., 29921878., 29921878.,\n", + " 29921878., 29921878., 29921878., 29921878., 29921878., 29921878.,\n", + " 29921878., 29921878., 29921878., 29921878.],\n", + " [29921878., 29921878., 29921878., 29921878., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0.],\n", + " [ 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0.],\n", + " [ 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0.],\n", + " [ 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0.],\n", + " [ 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0.],\n", + " [ 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0.],\n", + " [ 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0.],\n", + " [ 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0.],\n", + " [ 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0.],\n", + " [ 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0., 0., 0.,\n", + " 0., 0., 0., 0.]], dtype=float32)" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "amplitudes = d.FPGA_wg_amplitude_R\n", + "print(\"Old values:\\n\", amplitudes)\n", + "amplitudes[9][0] = 1.0\n", + "amplitudes[9][1] = 1.99\n", + "amplitudes[10][0] = 0.5\n", + "print(\"Values to be set:\\n\", amplitudes)\n", + "d.FPGA_wg_amplitude_RW = amplitudes\n", + "time.sleep(7)\n", + "print(\"Values read back after setting:\\n\", d.FPGA_wg_amplitude_R)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9b1bbd3e", + "metadata": {}, + "outputs": [], + "source": [ + "frequencies = d.FPGA_wg_frequency_R\n", + "print(\"Old values:\\n\", frequencies)\n", + "frequencies[9][0] = 19000000\n", + "frequencies[9][1] = 20000000\n", + "frequencies[10][0] = 22000000\n", + "print(\"Values to be set:\\n\", frequencies)\n", + "d.FPGA_wg_frequency_RW = frequencies\n", + "print(\"Values read back after setting:\\n\", d.FPGA_wg_frequency_R)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "StationControl", + "language": "python", + "name": "stationcontrol" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/jupyter-notebooks/WG_test.ipynb b/jupyter-notebooks/WG_test.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..5c68571046f886bded570ad37edec14e0a1a1fb0 --- /dev/null +++ b/jupyter-notebooks/WG_test.ipynb @@ -0,0 +1,1623 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 27, + "id": "2f0106f1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(tango._tango.DevState.ON, tango._tango.DevState.ON)" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#d=DeviceProxy(\"STAT/SST/1\")\n", + "#s=DeviceProxy(\"STAT/SDP/1\")\n", + "#d.off(),d.initialise(), d.on()\n", + "s.initialise(), s.on()\n", + "(d.state(), s.state())" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "5aa9bf0a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[('10.99.250.250', ''),\n", + " ('10.99.250.250', ''),\n", + " ('10.99.250.250', ''),\n", + " ('10.99.250.250', ''),\n", + " ('10.99.250.250', ''),\n", + " ('10.99.250.250', ''),\n", + " ('10.99.250.250', ''),\n", + " ('10.99.250.250', ''),\n", + " ('10.99.250.250', '10.99.250.250'),\n", + " ('10.99.250.250', '10.99.250.250'),\n", + " ('10.99.250.250', '10.99.250.250'),\n", + " ('10.99.250.250', '10.99.250.250'),\n", + " ('10.99.250.250', ''),\n", + " ('10.99.250.250', ''),\n", + " ('10.99.250.250', ''),\n", + " ('10.99.250.250', '')]" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "d.FPGA_sst_offload_enable_RW = [False]*16\n", + "d.FPGA_sst_offload_hdr_ip_destination_address_RW=[\"10.99.250.250\"]*16\n", + "list(zip(d.FPGA_sst_offload_hdr_ip_destination_address_RW,d.FPGA_sst_offload_hdr_ip_destination_address_R))" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "c7605d97", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[('6c:2b:59:97:be:dd', ''),\n", + " ('6c:2b:59:97:be:dd', ''),\n", + " ('6c:2b:59:97:be:dd', ''),\n", + " ('6c:2b:59:97:be:dd', ''),\n", + " ('6c:2b:59:97:be:dd', ''),\n", + " ('6c:2b:59:97:be:dd', ''),\n", + " ('6c:2b:59:97:be:dd', ''),\n", + " ('6c:2b:59:97:be:dd', ''),\n", + " ('6c:2b:59:97:be:dd', '6c:2b:59:97:be:dd'),\n", + " ('6c:2b:59:97:be:dd', '6c:2b:59:97:be:dd'),\n", + " ('6c:2b:59:97:be:dd', '6c:2b:59:97:be:dd'),\n", + " ('6c:2b:59:97:be:dd', '6c:2b:59:97:be:dd'),\n", + " ('6c:2b:59:97:be:dd', ''),\n", + " ('6c:2b:59:97:be:dd', ''),\n", + " ('6c:2b:59:97:be:dd', ''),\n", + " ('6c:2b:59:97:be:dd', '')]" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "d.FPGA_sst_offload_hdr_eth_destination_mac_RW=[\"6c:2b:59:97:be:dd\"]*16\n", + "list(zip(d.FPGA_sst_offload_hdr_eth_destination_mac_RW,d.FPGA_sst_offload_hdr_eth_destination_mac_R))" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "305b548a", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "set_defaults", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mAttributeError\u001B[0m Traceback (most recent call last)", + "\u001B[0;32m/tmp/ipykernel_21/1160776047.py\u001B[0m in \u001B[0;36m<module>\u001B[0;34m\u001B[0m\n\u001B[0;32m----> 1\u001B[0;31m \u001B[0md\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mset_defaults\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m", + "\u001B[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001B[0m in \u001B[0;36m__DeviceProxy__getattr\u001B[0;34m(self, name)\u001B[0m\n\u001B[1;32m 353\u001B[0m \u001B[0;32mreturn\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mread_pipe\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mname\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 354\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 355\u001B[0;31m \u001B[0msix\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mraise_from\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mAttributeError\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mname\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mcause\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 356\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 357\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/usr/local/lib/python3.7/dist-packages/six.py\u001B[0m in \u001B[0;36mraise_from\u001B[0;34m(value, from_value)\u001B[0m\n", + "\u001B[0;31mAttributeError\u001B[0m: set_defaults" + ] + } + ], + "source": [ + "d.set_defaults()" + ] + }, + { + "cell_type": "code", + "execution_count": 638, + "id": "09e9b5e6", + "metadata": {}, + "outputs": [], + "source": [ + "s.set_defaults()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a24c8a66", + "metadata": {}, + "outputs": [], + "source": [ + "s.FPGA_" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "9ed6b96d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[False, False, False, False, False, False, False, False, False,\n", + " False, False, False],\n", + " [False, False, False, False, False, False, False, False, False,\n", + " False, False, False],\n", + " [False, False, False, False, False, False, False, False, False,\n", + " False, False, False],\n", + " [False, False, False, False, False, False, False, False, False,\n", + " False, False, False],\n", + " [False, False, False, False, False, False, False, False, False,\n", + " False, False, False],\n", + " [False, False, False, False, False, False, False, False, False,\n", + " False, False, False],\n", + " [False, False, False, False, False, False, False, False, False,\n", + " False, False, False],\n", + " [False, False, False, False, False, False, False, False, False,\n", + " False, False, False],\n", + " [ True, True, True, True, True, True, True, True, True,\n", + " True, True, True],\n", + " [ True, True, True, True, True, True, True, True, True,\n", + " True, True, True],\n", + " [ True, True, True, True, True, True, True, True, True,\n", + " True, True, True],\n", + " [ True, True, True, True, True, True, True, True, True,\n", + " True, True, True],\n", + " [False, False, False, False, False, False, False, False, False,\n", + " False, False, False],\n", + " [False, False, False, False, False, False, False, False, False,\n", + " False, False, False],\n", + " [False, False, False, False, False, False, False, False, False,\n", + " False, False, False],\n", + " [False, False, False, False, False, False, False, False, False,\n", + " False, False, False]])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s.FPGA_wg_enable_RW=[[True]*12]*16\n", + "s.FPGA_wg_enable_R" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "766f818f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('',\n", + " '',\n", + " '',\n", + " '',\n", + " '',\n", + " '',\n", + " '',\n", + " '',\n", + " '10.1.1.1',\n", + " '10.1.1.1',\n", + " '10.1.1.1',\n", + " '10.1.1.1',\n", + " '',\n", + " '',\n", + " '',\n", + " '')" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "d.FPGA_sst_offload_hdr_ip_destination_address_R" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "c0d78129", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "('', '', '', '', '', '', '', '', '20-21-08T10.51.20_808728114_lofar2_unb2b_sdp_station_xsub_one', '20-21-08T10.51.20_808728114_lofar2_unb2b_sdp_station_xsub_one', '20-21-08T10.51.20_808728114_lofar2_unb2b_sdp_station_xsub_one', '20-21-08T10.51.20_808728114_lofar2_unb2b_sdp_station_xsub_one', '', '', '', '')\n" + ] + } + ], + "source": [ + "print(s.FPGA_firmware_version_R)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "82fe3420", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[False False False False False False False False True False True True\n", + " False False False False]\n" + ] + } + ], + "source": [ + "d.FPGA_processing_enable_RW=[True]*16\n", + "print(s.FPGA_processing_enable_R)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "c482f116", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ True, True, True, True, True, True, True, True, False,\n", + " False, False, False, True, True, True, True])" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s.TR_fpga_communication_error_R" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "1ff83508", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([False, False, False, False, False, False, False, False, True,\n", + " True, True, True, False, False, False, False]),\n", + " array([False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, True, True, True,\n", + " True, True, True, True, True, True, True, True, True,\n", + " True, True, True, True, True, True, True, True, True,\n", + " True, True, True, True, True, True, True, True, True,\n", + " True, True, True, True, True, True, True, True, True,\n", + " True, True, True, True, True, True, True, True, True,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False, False, False, False, False, False, False,\n", + " False, False, False]))" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#s.FPGA_wg_enable_RW=[[False]*16]*12\n", + "s.TR_fpga_mask_R, s.FPGA_wg_enable_R.flatten()" + ] + }, + { + "cell_type": "code", + "execution_count": 160, + "id": "13783e0f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Old values:\n", + " [[False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [ True True True True True True True True True True True True]\n", + " [ True True True True True True True True True True True True]\n", + " [ True True True True True True True True True True True True]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]]\n", + "Values to be set:\n", + " [[False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [ True True True True True True True True True True True True]\n", + " [ True True True True True True True True True True True True]\n", + " [ True True True True True True True True True True True True]\n", + " [ True True True True True True True True True True True True]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]]\n", + "Values read back after setting:\n", + " [[False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [ True True True True True True True True True True True True]\n", + " [ True True True True True True True True True True True True]\n", + " [ True True True True True True True True True True True True]\n", + " [ True True True True True True True True True True True True]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]]\n" + ] + } + ], + "source": [ + "wgswitches = s.FPGA_wg_enable_R\n", + "print(\"Old values:\\n\", wgswitches)\n", + "wgswitches[8] = True\n", + "#wgswitches[9][0] = True\n", + "#wgswitches[10][0] = True\n", + "print(\"Values to be set:\\n\", wgswitches)\n", + "s.FPGA_wg_enable_RW =wgswitches\n", + "print(\"Values read back after setting:\\n\",s.FPGA_wg_enable_R)" + ] + }, + { + "cell_type": "code", + "execution_count": 167, + "id": "4f0c3783", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Values read back after resetting:\n", + " [[False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [ True True True True True True True True True True True True]\n", + " [ True True True True True True True True True True True True]\n", + " [ True True True True True True True True True True True True]\n", + " [ True True True True True True True True True True True True]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]\n", + " [False False False False False False False False False False False False]]\n" + ] + } + ], + "source": [ + "#Reset WG: needed after frequence changes\n", + "wgswitches = s.FPGA_wg_enable_R\n", + "wgswitches[8] = False\n", + "s.FPGA_wg_enable_RW =wgswitches\n", + "wgswitches[8] = True\n", + "s.FPGA_wg_enable_RW =wgswitches\n", + "print(\"Values read back after resetting:\\n\",s.FPGA_wg_enable_R)" + ] + }, + { + "cell_type": "code", + "execution_count": 140, + "id": "e2bf77e4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Old values:\n", + " [[0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0.09999084 0.09999084 0.09999084 0.09999084 0.09999084 0.09999084\n", + " 0.09999084 0.09999084 0.09999084 0.09999084 0.09999084 0.09999084]\n", + " [1. 1. 1. 1. 1. 1.\n", + " 1. 1. 1. 1. 1. 1. ]\n", + " [1. 1. 1. 1. 1. 1.\n", + " 1. 1. 1. 1. 1. 1. ]\n", + " [1. 1. 1. 1. 1. 1.\n", + " 1. 1. 1. 1. 1. 1. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]]\n", + "Values to be set:\n", + " [[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ]\n", + " [0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01]\n", + " [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. ]\n", + " [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. ]\n", + " [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. ]\n", + " [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ]]\n", + "Values read back after setting:\n", + " [[0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0.00999451 0.00999451 0.00999451 0.00999451 0.00999451 0.00999451\n", + " 0.00999451 0.00999451 0.00999451 0.00999451 0.00999451 0.00999451]\n", + " [1. 1. 1. 1. 1. 1.\n", + " 1. 1. 1. 1. 1. 1. ]\n", + " [1. 1. 1. 1. 1. 1.\n", + " 1. 1. 1. 1. 1. 1. ]\n", + " [1. 1. 1. 1. 1. 1.\n", + " 1. 1. 1. 1. 1. 1. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0. 0. ]]\n" + ] + } + ], + "source": [ + "amplitudes = s.FPGA_wg_amplitude_R\n", + "print(\"Old values:\\n\", amplitudes)\n", + "#amplitudes[8] = 1\n", + "#amplitudes[10] = 1.0\n", + "#amplitudes[11] = 1.0\n", + "amplitudes[8] = 0.01\n", + "#amplitudes[8][1] = 0.5\n", + "print(\"Values to be set:\\n\", amplitudes)\n", + "s.FPGA_wg_amplitude_RW = amplitudes\n", + "print(\"Values read back after setting:\\n\", s.FPGA_wg_amplitude_R)" + ] + }, + { + "cell_type": "code", + "execution_count": 166, + "id": "d499a2f6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Old values:\n", + " [[ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [19999999.92549419 19999999.92549419 19999999.92549419 19999999.92549419\n", + " 19999999.92549419 19999999.92549419 19999999.92549419 19999999.92549419\n", + " 19999999.92549419 19999999.92549419 19999999.92549419 19999999.92549419]\n", + " [18999999.94784594 19999999.92549419 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [21999999.97392297 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]]\n", + "Values to be set:\n", + " [[ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [30000000. 30000000. 30000000. 30000000.\n", + " 30000000. 30000000. 30000000. 30000000.\n", + " 30000000. 30000000. 30000000. 30000000. ]\n", + " [18999999.94784594 19999999.92549419 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [21999999.97392297 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]]\n", + "Values read back after setting:\n", + " [[ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [29999999.98137355 29999999.98137355 29999999.98137355 29999999.98137355\n", + " 29999999.98137355 29999999.98137355 29999999.98137355 29999999.98137355\n", + " 29999999.98137355 29999999.98137355 29999999.98137355 29999999.98137355]\n", + " [18999999.94784594 19999999.92549419 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [21999999.97392297 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]\n", + " [ 0. 0. 0. 0.\n", + " 0. 0. 0. 0.\n", + " 0. 0. 0. 0. ]]\n" + ] + } + ], + "source": [ + "frequencies = s.FPGA_wg_frequency_R\n", + "print(\"Old values:\\n\", frequencies)\n", + "#frequencies[8][0] = 10000000\n", + "frequencies[8] = 30000000\n", + "#frequencies[8][2] = 15000000\n", + "#frequencies[8][3] = 22000000\n", + "#frequencies[10][0] = 22000000\n", + "print(\"Values to be set:\\n\", frequencies)\n", + "s.FPGA_wg_frequency_RW = frequencies\n", + "print(\"Values read back after setting:\\n\", s.FPGA_wg_frequency_R)" + ] + }, + { + "cell_type": "code", + "execution_count": 132, + "id": "cac5c354", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Old values:\n", + " [[ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 29.99816895 59.99633789 90. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 1.03271484 20.14892578 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [129.99572754 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]]\n", + "Values to be set:\n", + " [[ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 1.03271484 20.14892578 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [129.99572754 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]]\n", + "Values read back after setting:\n", + " [[ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 1.03271484 20.14892578 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [129.99572754 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]\n", + " [ 0. 0. 0. 0. 0.\n", + " 0. 0. 0. 0. 0.\n", + " 0. 0. ]]\n" + ] + } + ], + "source": [ + "phases = s.FPGA_wg_phase_R\n", + "print(\"Old values:\\n\", phases)\n", + "phases[8] = 0\n", + "#phases[8][1] = 30\n", + "#phases[8][2] = 60\n", + "#phases[8][3] = 90\n", + "#phases[10][0] = 130\n", + "print(\"Values to be set:\\n\", phases)\n", + "s.FPGA_wg_phase_RW = phases\n", + "print(\"Values read back after setting:\\n\", s.FPGA_wg_phase_R)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "aff7476f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[False False False False False False False False True False False False\n", + " False False False False]\n" + ] + } + ], + "source": [ + "Enable=[False]*16\n", + "Enable[8]=True\n", + "d.FPGA_sst_offload_enable_RW=Enable\n", + "\n", + "print(d.FPGA_sst_offload_enable_R)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "97c2d815", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "('', '', '', '', '', '', '', '', '6c:2b:59:97:be:dd', '6c:2b:59:97:be:dd', '6c:2b:59:97:be:dd', '6c:2b:59:97:be:dd', '', '', '', '')\n", + "('', '', '', '', '', '', '', '', '10.99.250.250', '10.99.250.250', '10.99.250.250', '10.99.250.250', '', '', '', '')\n" + ] + } + ], + "source": [ + "print(d.FPGA_sst_offload_hdr_eth_destination_mac_R)\n", + "print(d.FPGA_sst_offload_hdr_ip_destination_address_R)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "bc3e0ebf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 0 0 0 0 0 0 0 0 5001 5001 5001 5001 0 0\n", + " 0 0]\n" + ] + } + ], + "source": [ + "prt=d.FPGA_sst_offload_hdr_udp_destination_port_R\n", + "print(prt)\n", + "#prt[0]=5001\n", + "#d.FPGA_sst_offload_hdr_udp_destination_port_RW=prt" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "25f7d1fa", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(456, '2021-08-30 08:13:14')" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import datetime\n", + "(d.nof_packets_received_R, datetime.datetime.fromtimestamp(d.last_packet_timestamp_r).isoformat(' '))" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "b95d0f28", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "d.nof_invalid_packets_R" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "f2db262d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "d.nof_packets_dropped_R" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "c81ad8d5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=uint64)" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "d.nof_payload_errors_R" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "19cfc04a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 14985, 14985, 14984,\n", + " 14985, 14985, 14986, 14982, 14986, 14985, 14987, 14983, 14987,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0], dtype=uint64)" + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "d.nof_valid_payloads_R" + ] + }, + { + "cell_type": "code", + "execution_count": 131, + "id": "b3f77e0b", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Antennas for which we got SSTs: [96, 97, 98, 99, 100]\n" + ] + } + ], + "source": [ + "print(\"Antennas for which we got SSTs:\",[i for i,x in enumerate(d.sst_r) if x[0] != 0])" + ] + }, + { + "cell_type": "code", + "execution_count": 112, + "id": "c483f89c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Antennas values [ 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 2854 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 224841 0 0 0\n", + " 20247 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0\n", + " 190973 0]\n" + ] + } + ], + "source": [ + "print(\"Antennas values\", d.sst_r[97])" + ] + }, + { + "cell_type": "code", + "execution_count": 168, + "id": "aabeb1b7", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " <div class=\"bk-root\">\n", + " <a href=\"https://bokeh.org\" target=\"_blank\" class=\"bk-logo bk-logo-small bk-logo-notebook\"></a>\n", + " <span id=\"31090\">Loading BokehJS ...</span>\n", + " </div>" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "\n", + "(function(root) {\n", + " function now() {\n", + " return new Date();\n", + " }\n", + "\n", + " var force = true;\n", + "\n", + " if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n", + " root._bokeh_onload_callbacks = [];\n", + " root._bokeh_is_loading = undefined;\n", + " }\n", + "\n", + " var JS_MIME_TYPE = 'application/javascript';\n", + " var HTML_MIME_TYPE = 'text/html';\n", + " var EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json';\n", + " var CLASS_NAME = 'output_bokeh rendered_html';\n", + "\n", + " /**\n", + " * Render data to the DOM node\n", + " */\n", + " function render(props, node) {\n", + " var script = document.createElement(\"script\");\n", + " node.appendChild(script);\n", + " }\n", + "\n", + " /**\n", + " * Handle when an output is cleared or removed\n", + " */\n", + " function handleClearOutput(event, handle) {\n", + " var cell = handle.cell;\n", + "\n", + " var id = cell.output_area._bokeh_element_id;\n", + " var server_id = cell.output_area._bokeh_server_id;\n", + " // Clean up Bokeh references\n", + " if (id != null && id in Bokeh.index) {\n", + " Bokeh.index[id].model.document.clear();\n", + " delete Bokeh.index[id];\n", + " }\n", + "\n", + " if (server_id !== undefined) {\n", + " // Clean up Bokeh references\n", + " var cmd = \"from bokeh.io.state import curstate; print(curstate().uuid_to_server['\" + server_id + \"'].get_sessions()[0].document.roots[0]._id)\";\n", + " cell.notebook.kernel.execute(cmd, {\n", + " iopub: {\n", + " output: function(msg) {\n", + " var id = msg.content.text.trim();\n", + " if (id in Bokeh.index) {\n", + " Bokeh.index[id].model.document.clear();\n", + " delete Bokeh.index[id];\n", + " }\n", + " }\n", + " }\n", + " });\n", + " // Destroy server and session\n", + " var cmd = \"import bokeh.io.notebook as ion; ion.destroy_server('\" + server_id + \"')\";\n", + " cell.notebook.kernel.execute(cmd);\n", + " }\n", + " }\n", + "\n", + " /**\n", + " * Handle when a new output is added\n", + " */\n", + " function handleAddOutput(event, handle) {\n", + " var output_area = handle.output_area;\n", + " var output = handle.output;\n", + "\n", + " // limit handleAddOutput to display_data with EXEC_MIME_TYPE content only\n", + " if ((output.output_type != \"display_data\") || (!Object.prototype.hasOwnProperty.call(output.data, EXEC_MIME_TYPE))) {\n", + " return\n", + " }\n", + "\n", + " var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n", + "\n", + " if (output.metadata[EXEC_MIME_TYPE][\"id\"] !== undefined) {\n", + " toinsert[toinsert.length - 1].firstChild.textContent = output.data[JS_MIME_TYPE];\n", + " // store reference to embed id on output_area\n", + " output_area._bokeh_element_id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n", + " }\n", + " if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n", + " var bk_div = document.createElement(\"div\");\n", + " bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n", + " var script_attrs = bk_div.children[0].attributes;\n", + " for (var i = 0; i < script_attrs.length; i++) {\n", + " toinsert[toinsert.length - 1].firstChild.setAttribute(script_attrs[i].name, script_attrs[i].value);\n", + " toinsert[toinsert.length - 1].firstChild.textContent = bk_div.children[0].textContent\n", + " }\n", + " // store reference to server id on output_area\n", + " output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n", + " }\n", + " }\n", + "\n", + " function register_renderer(events, OutputArea) {\n", + "\n", + " function append_mime(data, metadata, element) {\n", + " // create a DOM node to render to\n", + " var toinsert = this.create_output_subarea(\n", + " metadata,\n", + " CLASS_NAME,\n", + " EXEC_MIME_TYPE\n", + " );\n", + " this.keyboard_manager.register_events(toinsert);\n", + " // Render to node\n", + " var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n", + " render(props, toinsert[toinsert.length - 1]);\n", + " element.append(toinsert);\n", + " return toinsert\n", + " }\n", + "\n", + " /* Handle when an output is cleared or removed */\n", + " events.on('clear_output.CodeCell', handleClearOutput);\n", + " events.on('delete.Cell', handleClearOutput);\n", + "\n", + " /* Handle when a new output is added */\n", + " events.on('output_added.OutputArea', handleAddOutput);\n", + "\n", + " /**\n", + " * Register the mime type and append_mime function with output_area\n", + " */\n", + " OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n", + " /* Is output safe? */\n", + " safe: true,\n", + " /* Index of renderer in `output_area.display_order` */\n", + " index: 0\n", + " });\n", + " }\n", + "\n", + " // register the mime type if in Jupyter Notebook environment and previously unregistered\n", + " if (root.Jupyter !== undefined) {\n", + " var events = require('base/js/events');\n", + " var OutputArea = require('notebook/js/outputarea').OutputArea;\n", + "\n", + " if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n", + " register_renderer(events, OutputArea);\n", + " }\n", + " }\n", + "\n", + " \n", + " if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n", + " root._bokeh_timeout = Date.now() + 5000;\n", + " root._bokeh_failed_load = false;\n", + " }\n", + "\n", + " var NB_LOAD_WARNING = {'data': {'text/html':\n", + " \"<div style='background-color: #fdd'>\\n\"+\n", + " \"<p>\\n\"+\n", + " \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n", + " \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n", + " \"</p>\\n\"+\n", + " \"<ul>\\n\"+\n", + " \"<li>re-rerun `output_notebook()` to attempt to load from CDN again, or</li>\\n\"+\n", + " \"<li>use INLINE resources instead, as so:</li>\\n\"+\n", + " \"</ul>\\n\"+\n", + " \"<code>\\n\"+\n", + " \"from bokeh.resources import INLINE\\n\"+\n", + " \"output_notebook(resources=INLINE)\\n\"+\n", + " \"</code>\\n\"+\n", + " \"</div>\"}};\n", + "\n", + " function display_loaded() {\n", + " var el = document.getElementById(\"31090\");\n", + " if (el != null) {\n", + " el.textContent = \"BokehJS is loading...\";\n", + " }\n", + " if (root.Bokeh !== undefined) {\n", + " if (el != null) {\n", + " el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n", + " }\n", + " } else if (Date.now() < root._bokeh_timeout) {\n", + " setTimeout(display_loaded, 100)\n", + " }\n", + " }\n", + "\n", + "\n", + " function run_callbacks() {\n", + " try {\n", + " root._bokeh_onload_callbacks.forEach(function(callback) {\n", + " if (callback != null)\n", + " callback();\n", + " });\n", + " } finally {\n", + " delete root._bokeh_onload_callbacks\n", + " }\n", + " console.debug(\"Bokeh: all callbacks have finished\");\n", + " }\n", + "\n", + " function load_libs(css_urls, js_urls, callback) {\n", + " if (css_urls == null) css_urls = [];\n", + " if (js_urls == null) js_urls = [];\n", + "\n", + " root._bokeh_onload_callbacks.push(callback);\n", + " if (root._bokeh_is_loading > 0) {\n", + " console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n", + " return null;\n", + " }\n", + " if (js_urls == null || js_urls.length === 0) {\n", + " run_callbacks();\n", + " return null;\n", + " }\n", + " console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n", + " root._bokeh_is_loading = css_urls.length + js_urls.length;\n", + "\n", + " function on_load() {\n", + " root._bokeh_is_loading--;\n", + " if (root._bokeh_is_loading === 0) {\n", + " console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n", + " run_callbacks()\n", + " }\n", + " }\n", + "\n", + " function on_error(url) {\n", + " console.error(\"failed to load \" + url);\n", + " }\n", + "\n", + " for (let i = 0; i < css_urls.length; i++) {\n", + " const url = css_urls[i];\n", + " const element = document.createElement(\"link\");\n", + " element.onload = on_load;\n", + " element.onerror = on_error.bind(null, url);\n", + " element.rel = \"stylesheet\";\n", + " element.type = \"text/css\";\n", + " element.href = url;\n", + " console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n", + " document.body.appendChild(element);\n", + " }\n", + "\n", + " const hashes = {\"https://cdn.bokeh.org/bokeh/release/bokeh-2.3.3.min.js\": \"dM3QQsP+wXdHg42wTqW85BjZQdLNNIXqlPw/BgKoExPmTG7ZLML4EGqLMfqHT6ON\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.3.3.min.js\": \"8x57I4YuIfu8XyZfFo0XVr2WAT8EK4rh/uDe3wF7YuW2FNUSNEpJbsPaB1nJ2fz2\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.3.3.min.js\": \"3QTqdz9LyAm2i0sG5XTePsHec3UHWwVsrOL68SYRoAXsafvfAyqtQ+h440+qIBhS\"};\n", + "\n", + " for (let i = 0; i < js_urls.length; i++) {\n", + " const url = js_urls[i];\n", + " const element = document.createElement('script');\n", + " element.onload = on_load;\n", + " element.onerror = on_error.bind(null, url);\n", + " element.async = false;\n", + " element.src = url;\n", + " if (url in hashes) {\n", + " element.crossOrigin = \"anonymous\";\n", + " element.integrity = \"sha384-\" + hashes[url];\n", + " }\n", + " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", + " document.head.appendChild(element);\n", + " }\n", + " };\n", + "\n", + " function inject_raw_css(css) {\n", + " const element = document.createElement(\"style\");\n", + " element.appendChild(document.createTextNode(css));\n", + " document.body.appendChild(element);\n", + " }\n", + "\n", + " \n", + " var js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-2.3.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.3.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.3.3.min.js\"];\n", + " var css_urls = [];\n", + " \n", + "\n", + " var inline_js = [\n", + " function(Bokeh) {\n", + " Bokeh.set_log_level(\"info\");\n", + " },\n", + " function(Bokeh) {\n", + " \n", + " \n", + " }\n", + " ];\n", + "\n", + " function run_inline_js() {\n", + " \n", + " if (root.Bokeh !== undefined || force === true) {\n", + " \n", + " for (var i = 0; i < inline_js.length; i++) {\n", + " inline_js[i].call(root, root.Bokeh);\n", + " }\n", + " if (force === true) {\n", + " display_loaded();\n", + " }} else if (Date.now() < root._bokeh_timeout) {\n", + " setTimeout(run_inline_js, 100);\n", + " } else if (!root._bokeh_failed_load) {\n", + " console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n", + " root._bokeh_failed_load = true;\n", + " } else if (force !== true) {\n", + " var cell = $(document.getElementById(\"31090\")).parents('.cell').data().cell;\n", + " cell.output_area.append_execute_result(NB_LOAD_WARNING)\n", + " }\n", + "\n", + " }\n", + "\n", + " if (root._bokeh_is_loading === 0) {\n", + " console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n", + " run_inline_js();\n", + " } else {\n", + " load_libs(css_urls, js_urls, function() {\n", + " console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n", + " run_inline_js();\n", + " });\n", + " }\n", + "}(window));" + ], + "application/vnd.bokehjs_load.v0+json": "\n(function(root) {\n function now() {\n return new Date();\n }\n\n var force = true;\n\n if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n root._bokeh_onload_callbacks = [];\n root._bokeh_is_loading = undefined;\n }\n\n \n\n \n if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n var NB_LOAD_WARNING = {'data': {'text/html':\n \"<div style='background-color: #fdd'>\\n\"+\n \"<p>\\n\"+\n \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n \"</p>\\n\"+\n \"<ul>\\n\"+\n \"<li>re-rerun `output_notebook()` to attempt to load from CDN again, or</li>\\n\"+\n \"<li>use INLINE resources instead, as so:</li>\\n\"+\n \"</ul>\\n\"+\n \"<code>\\n\"+\n \"from bokeh.resources import INLINE\\n\"+\n \"output_notebook(resources=INLINE)\\n\"+\n \"</code>\\n\"+\n \"</div>\"}};\n\n function display_loaded() {\n var el = document.getElementById(\"31090\");\n if (el != null) {\n el.textContent = \"BokehJS is loading...\";\n }\n if (root.Bokeh !== undefined) {\n if (el != null) {\n el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(display_loaded, 100)\n }\n }\n\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n\n root._bokeh_onload_callbacks.push(callback);\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls == null || js_urls.length === 0) {\n run_callbacks();\n return null;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n root._bokeh_is_loading = css_urls.length + js_urls.length;\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n\n function on_error(url) {\n console.error(\"failed to load \" + url);\n }\n\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error.bind(null, url);\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n }\n\n const hashes = {\"https://cdn.bokeh.org/bokeh/release/bokeh-2.3.3.min.js\": \"dM3QQsP+wXdHg42wTqW85BjZQdLNNIXqlPw/BgKoExPmTG7ZLML4EGqLMfqHT6ON\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.3.3.min.js\": \"8x57I4YuIfu8XyZfFo0XVr2WAT8EK4rh/uDe3wF7YuW2FNUSNEpJbsPaB1nJ2fz2\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.3.3.min.js\": \"3QTqdz9LyAm2i0sG5XTePsHec3UHWwVsrOL68SYRoAXsafvfAyqtQ+h440+qIBhS\"};\n\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error.bind(null, url);\n element.async = false;\n element.src = url;\n if (url in hashes) {\n element.crossOrigin = \"anonymous\";\n element.integrity = \"sha384-\" + hashes[url];\n }\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n \n var js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-2.3.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.3.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.3.3.min.js\"];\n var css_urls = [];\n \n\n var inline_js = [\n function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\n function(Bokeh) {\n \n \n }\n ];\n\n function run_inline_js() {\n \n if (root.Bokeh !== undefined || force === true) {\n \n for (var i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\n if (force === true) {\n display_loaded();\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n } else if (force !== true) {\n var cell = $(document.getElementById(\"31090\")).parents('.cell').data().cell;\n cell.output_area.append_execute_result(NB_LOAD_WARNING)\n }\n\n }\n\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n run_inline_js();\n } else {\n load_libs(css_urls, js_urls, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n}(window));" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " <div class=\"bk-root\" id=\"732e77fe-65b7-4633-9104-3bae40cc4886\" data-root-id=\"30897\"></div>\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "(function(root) {\n", + " function embed_document(root) {\n", + " \n", + " var docs_json = {\"665d586c-411a-47fc-8844-15f332d3038a\":{\"defs\":[],\"roots\":{\"references\":[{\"attributes\":{\"below\":[{\"id\":\"30906\"}],\"center\":[{\"id\":\"30909\"},{\"id\":\"30913\"},{\"id\":\"30944\"}],\"height\":500,\"left\":[{\"id\":\"30910\"}],\"renderers\":[{\"id\":\"30931\"},{\"id\":\"30949\"},{\"id\":\"30968\"},{\"id\":\"30989\"},{\"id\":\"31012\"},{\"id\":\"31037\"},{\"id\":\"31064\"}],\"title\":{\"id\":\"30933\"},\"toolbar\":{\"id\":\"30921\"},\"width\":1000,\"x_range\":{\"id\":\"30898\"},\"x_scale\":{\"id\":\"30902\"},\"y_range\":{\"id\":\"30900\"},\"y_scale\":{\"id\":\"30904\"}},\"id\":\"30897\",\"subtype\":\"Figure\",\"type\":\"Plot\"},{\"attributes\":{},\"id\":\"30904\",\"type\":\"LogScale\"},{\"attributes\":{\"axis_label\":\"MHz\",\"formatter\":{\"id\":\"30940\"},\"major_label_policy\":{\"id\":\"30939\"},\"ticker\":{\"id\":\"30907\"}},\"id\":\"30906\",\"type\":\"LinearAxis\"},{\"attributes\":{},\"id\":\"30907\",\"type\":\"BasicTicker\"},{\"attributes\":{\"axis\":{\"id\":\"30906\"},\"ticker\":null},\"id\":\"30909\",\"type\":\"Grid\"},{\"attributes\":{\"axis_label\":\"power\",\"formatter\":{\"id\":\"30937\"},\"major_label_policy\":{\"id\":\"30936\"},\"ticker\":{\"id\":\"30911\"}},\"id\":\"30910\",\"type\":\"LogAxis\"},{\"attributes\":{\"num_minor_ticks\":10},\"id\":\"30911\",\"type\":\"LogTicker\"},{\"attributes\":{\"axis\":{\"id\":\"30910\"},\"dimension\":1,\"ticker\":null},\"id\":\"30913\",\"type\":\"Grid\"},{\"attributes\":{\"data_source\":{\"id\":\"30928\"},\"glyph\":{\"id\":\"30929\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"30930\"},\"view\":{\"id\":\"30932\"}},\"id\":\"30931\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"source\":{\"id\":\"30928\"}},\"id\":\"30932\",\"type\":\"CDSView\"},{\"attributes\":{},\"id\":\"30914\",\"type\":\"PanTool\"},{\"attributes\":{},\"id\":\"30915\",\"type\":\"WheelZoomTool\"},{\"attributes\":{\"overlay\":{\"id\":\"30920\"}},\"id\":\"30916\",\"type\":\"BoxZoomTool\"},{\"attributes\":{},\"id\":\"30917\",\"type\":\"SaveTool\"},{\"attributes\":{},\"id\":\"30918\",\"type\":\"ResetTool\"},{\"attributes\":{},\"id\":\"30919\",\"type\":\"HelpTool\"},{\"attributes\":{},\"id\":\"30961\",\"type\":\"Selection\"},{\"attributes\":{\"line_alpha\":0.1,\"line_color\":\"brown\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"30930\",\"type\":\"Line\"},{\"attributes\":{\"active_multi\":null,\"tools\":[{\"id\":\"30914\"},{\"id\":\"30915\"},{\"id\":\"30916\"},{\"id\":\"30917\"},{\"id\":\"30918\"},{\"id\":\"30919\"}]},\"id\":\"30921\",\"type\":\"Toolbar\"},{\"attributes\":{\"bottom_units\":\"screen\",\"fill_alpha\":0.5,\"fill_color\":\"lightgrey\",\"left_units\":\"screen\",\"level\":\"overlay\",\"line_alpha\":1.0,\"line_color\":\"black\",\"line_dash\":[4,4],\"line_width\":2,\"right_units\":\"screen\",\"syncable\":false,\"top_units\":\"screen\"},\"id\":\"30920\",\"type\":\"BoxAnnotation\"},{\"attributes\":{},\"id\":\"30933\",\"type\":\"Title\"},{\"attributes\":{},\"id\":\"30936\",\"type\":\"AllLabels\"},{\"attributes\":{\"ticker\":null},\"id\":\"30937\",\"type\":\"LogTickFormatter\"},{\"attributes\":{},\"id\":\"30939\",\"type\":\"AllLabels\"},{\"attributes\":{},\"id\":\"30940\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{\"data\":{\"x\":[0.0,0.1953125,0.390625,0.5859375,0.78125,0.9765625,1.171875,1.3671875,1.5625,1.7578125,1.953125,2.1484375,2.34375,2.5390625,2.734375,2.9296875,3.125,3.3203125,3.515625,3.7109375,3.90625,4.1015625,4.296875,4.4921875,4.6875,4.8828125,5.078125,5.2734375,5.46875,5.6640625,5.859375,6.0546875,6.25,6.4453125,6.640625,6.8359375,7.03125,7.2265625,7.421875,7.6171875,7.8125,8.0078125,8.203125,8.3984375,8.59375,8.7890625,8.984375,9.1796875,9.375,9.5703125,9.765625,9.9609375,10.15625,10.3515625,10.546875,10.7421875,10.9375,11.1328125,11.328125,11.5234375,11.71875,11.9140625,12.109375,12.3046875,12.5,12.6953125,12.890625,13.0859375,13.28125,13.4765625,13.671875,13.8671875,14.0625,14.2578125,14.453125,14.6484375,14.84375,15.0390625,15.234375,15.4296875,15.625,15.8203125,16.015625,16.2109375,16.40625,16.6015625,16.796875,16.9921875,17.1875,17.3828125,17.578125,17.7734375,17.96875,18.1640625,18.359375,18.5546875,18.75,18.9453125,19.140625,19.3359375,19.53125,19.7265625,19.921875,20.1171875,20.3125,20.5078125,20.703125,20.8984375,21.09375,21.2890625,21.484375,21.6796875,21.875,22.0703125,22.265625,22.4609375,22.65625,22.8515625,23.046875,23.2421875,23.4375,23.6328125,23.828125,24.0234375,24.21875,24.4140625,24.609375,24.8046875,25.0,25.1953125,25.390625,25.5859375,25.78125,25.9765625,26.171875,26.3671875,26.5625,26.7578125,26.953125,27.1484375,27.34375,27.5390625,27.734375,27.9296875,28.125,28.3203125,28.515625,28.7109375,28.90625,29.1015625,29.296875,29.4921875,29.6875,29.8828125,30.078125,30.2734375,30.46875,30.6640625,30.859375,31.0546875,31.25,31.4453125,31.640625,31.8359375,32.03125,32.2265625,32.421875,32.6171875,32.8125,33.0078125,33.203125,33.3984375,33.59375,33.7890625,33.984375,34.1796875,34.375,34.5703125,34.765625,34.9609375,35.15625,35.3515625,35.546875,35.7421875,35.9375,36.1328125,36.328125,36.5234375,36.71875,36.9140625,37.109375,37.3046875,37.5,37.6953125,37.890625,38.0859375,38.28125,38.4765625,38.671875,38.8671875,39.0625,39.2578125,39.453125,39.6484375,39.84375,40.0390625,40.234375,40.4296875,40.625,40.8203125,41.015625,41.2109375,41.40625,41.6015625,41.796875,41.9921875,42.1875,42.3828125,42.578125,42.7734375,42.96875,43.1640625,43.359375,43.5546875,43.75,43.9453125,44.140625,44.3359375,44.53125,44.7265625,44.921875,45.1171875,45.3125,45.5078125,45.703125,45.8984375,46.09375,46.2890625,46.484375,46.6796875,46.875,47.0703125,47.265625,47.4609375,47.65625,47.8515625,48.046875,48.2421875,48.4375,48.6328125,48.828125,49.0234375,49.21875,49.4140625,49.609375,49.8046875,50.0,50.1953125,50.390625,50.5859375,50.78125,50.9765625,51.171875,51.3671875,51.5625,51.7578125,51.953125,52.1484375,52.34375,52.5390625,52.734375,52.9296875,53.125,53.3203125,53.515625,53.7109375,53.90625,54.1015625,54.296875,54.4921875,54.6875,54.8828125,55.078125,55.2734375,55.46875,55.6640625,55.859375,56.0546875,56.25,56.4453125,56.640625,56.8359375,57.03125,57.2265625,57.421875,57.6171875,57.8125,58.0078125,58.203125,58.3984375,58.59375,58.7890625,58.984375,59.1796875,59.375,59.5703125,59.765625,59.9609375,60.15625,60.3515625,60.546875,60.7421875,60.9375,61.1328125,61.328125,61.5234375,61.71875,61.9140625,62.109375,62.3046875,62.5,62.6953125,62.890625,63.0859375,63.28125,63.4765625,63.671875,63.8671875,64.0625,64.2578125,64.453125,64.6484375,64.84375,65.0390625,65.234375,65.4296875,65.625,65.8203125,66.015625,66.2109375,66.40625,66.6015625,66.796875,66.9921875,67.1875,67.3828125,67.578125,67.7734375,67.96875,68.1640625,68.359375,68.5546875,68.75,68.9453125,69.140625,69.3359375,69.53125,69.7265625,69.921875,70.1171875,70.3125,70.5078125,70.703125,70.8984375,71.09375,71.2890625,71.484375,71.6796875,71.875,72.0703125,72.265625,72.4609375,72.65625,72.8515625,73.046875,73.2421875,73.4375,73.6328125,73.828125,74.0234375,74.21875,74.4140625,74.609375,74.8046875,75.0,75.1953125,75.390625,75.5859375,75.78125,75.9765625,76.171875,76.3671875,76.5625,76.7578125,76.953125,77.1484375,77.34375,77.5390625,77.734375,77.9296875,78.125,78.3203125,78.515625,78.7109375,78.90625,79.1015625,79.296875,79.4921875,79.6875,79.8828125,80.078125,80.2734375,80.46875,80.6640625,80.859375,81.0546875,81.25,81.4453125,81.640625,81.8359375,82.03125,82.2265625,82.421875,82.6171875,82.8125,83.0078125,83.203125,83.3984375,83.59375,83.7890625,83.984375,84.1796875,84.375,84.5703125,84.765625,84.9609375,85.15625,85.3515625,85.546875,85.7421875,85.9375,86.1328125,86.328125,86.5234375,86.71875,86.9140625,87.109375,87.3046875,87.5,87.6953125,87.890625,88.0859375,88.28125,88.4765625,88.671875,88.8671875,89.0625,89.2578125,89.453125,89.6484375,89.84375,90.0390625,90.234375,90.4296875,90.625,90.8203125,91.015625,91.2109375,91.40625,91.6015625,91.796875,91.9921875,92.1875,92.3828125,92.578125,92.7734375,92.96875,93.1640625,93.359375,93.5546875,93.75,93.9453125,94.140625,94.3359375,94.53125,94.7265625,94.921875,95.1171875,95.3125,95.5078125,95.703125,95.8984375,96.09375,96.2890625,96.484375,96.6796875,96.875,97.0703125,97.265625,97.4609375,97.65625,97.8515625,98.046875,98.2421875,98.4375,98.6328125,98.828125,99.0234375,99.21875,99.4140625,99.609375,99.8046875],\"y\":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,532157,25,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,13313912,91696270071,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,806025,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,437811,61,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,29,585376,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},\"selected\":{\"id\":\"30982\"},\"selection_policy\":{\"id\":\"30983\"}},\"id\":\"30965\",\"type\":\"ColumnDataSource\"},{\"attributes\":{},\"id\":\"30962\",\"type\":\"UnionRenderers\"},{\"attributes\":{\"label\":{\"value\":\"input 97\"},\"renderers\":[{\"id\":\"30949\"}]},\"id\":\"30964\",\"type\":\"LegendItem\"},{\"attributes\":{\"data\":{\"x\":[0.0,0.1953125,0.390625,0.5859375,0.78125,0.9765625,1.171875,1.3671875,1.5625,1.7578125,1.953125,2.1484375,2.34375,2.5390625,2.734375,2.9296875,3.125,3.3203125,3.515625,3.7109375,3.90625,4.1015625,4.296875,4.4921875,4.6875,4.8828125,5.078125,5.2734375,5.46875,5.6640625,5.859375,6.0546875,6.25,6.4453125,6.640625,6.8359375,7.03125,7.2265625,7.421875,7.6171875,7.8125,8.0078125,8.203125,8.3984375,8.59375,8.7890625,8.984375,9.1796875,9.375,9.5703125,9.765625,9.9609375,10.15625,10.3515625,10.546875,10.7421875,10.9375,11.1328125,11.328125,11.5234375,11.71875,11.9140625,12.109375,12.3046875,12.5,12.6953125,12.890625,13.0859375,13.28125,13.4765625,13.671875,13.8671875,14.0625,14.2578125,14.453125,14.6484375,14.84375,15.0390625,15.234375,15.4296875,15.625,15.8203125,16.015625,16.2109375,16.40625,16.6015625,16.796875,16.9921875,17.1875,17.3828125,17.578125,17.7734375,17.96875,18.1640625,18.359375,18.5546875,18.75,18.9453125,19.140625,19.3359375,19.53125,19.7265625,19.921875,20.1171875,20.3125,20.5078125,20.703125,20.8984375,21.09375,21.2890625,21.484375,21.6796875,21.875,22.0703125,22.265625,22.4609375,22.65625,22.8515625,23.046875,23.2421875,23.4375,23.6328125,23.828125,24.0234375,24.21875,24.4140625,24.609375,24.8046875,25.0,25.1953125,25.390625,25.5859375,25.78125,25.9765625,26.171875,26.3671875,26.5625,26.7578125,26.953125,27.1484375,27.34375,27.5390625,27.734375,27.9296875,28.125,28.3203125,28.515625,28.7109375,28.90625,29.1015625,29.296875,29.4921875,29.6875,29.8828125,30.078125,30.2734375,30.46875,30.6640625,30.859375,31.0546875,31.25,31.4453125,31.640625,31.8359375,32.03125,32.2265625,32.421875,32.6171875,32.8125,33.0078125,33.203125,33.3984375,33.59375,33.7890625,33.984375,34.1796875,34.375,34.5703125,34.765625,34.9609375,35.15625,35.3515625,35.546875,35.7421875,35.9375,36.1328125,36.328125,36.5234375,36.71875,36.9140625,37.109375,37.3046875,37.5,37.6953125,37.890625,38.0859375,38.28125,38.4765625,38.671875,38.8671875,39.0625,39.2578125,39.453125,39.6484375,39.84375,40.0390625,40.234375,40.4296875,40.625,40.8203125,41.015625,41.2109375,41.40625,41.6015625,41.796875,41.9921875,42.1875,42.3828125,42.578125,42.7734375,42.96875,43.1640625,43.359375,43.5546875,43.75,43.9453125,44.140625,44.3359375,44.53125,44.7265625,44.921875,45.1171875,45.3125,45.5078125,45.703125,45.8984375,46.09375,46.2890625,46.484375,46.6796875,46.875,47.0703125,47.265625,47.4609375,47.65625,47.8515625,48.046875,48.2421875,48.4375,48.6328125,48.828125,49.0234375,49.21875,49.4140625,49.609375,49.8046875,50.0,50.1953125,50.390625,50.5859375,50.78125,50.9765625,51.171875,51.3671875,51.5625,51.7578125,51.953125,52.1484375,52.34375,52.5390625,52.734375,52.9296875,53.125,53.3203125,53.515625,53.7109375,53.90625,54.1015625,54.296875,54.4921875,54.6875,54.8828125,55.078125,55.2734375,55.46875,55.6640625,55.859375,56.0546875,56.25,56.4453125,56.640625,56.8359375,57.03125,57.2265625,57.421875,57.6171875,57.8125,58.0078125,58.203125,58.3984375,58.59375,58.7890625,58.984375,59.1796875,59.375,59.5703125,59.765625,59.9609375,60.15625,60.3515625,60.546875,60.7421875,60.9375,61.1328125,61.328125,61.5234375,61.71875,61.9140625,62.109375,62.3046875,62.5,62.6953125,62.890625,63.0859375,63.28125,63.4765625,63.671875,63.8671875,64.0625,64.2578125,64.453125,64.6484375,64.84375,65.0390625,65.234375,65.4296875,65.625,65.8203125,66.015625,66.2109375,66.40625,66.6015625,66.796875,66.9921875,67.1875,67.3828125,67.578125,67.7734375,67.96875,68.1640625,68.359375,68.5546875,68.75,68.9453125,69.140625,69.3359375,69.53125,69.7265625,69.921875,70.1171875,70.3125,70.5078125,70.703125,70.8984375,71.09375,71.2890625,71.484375,71.6796875,71.875,72.0703125,72.265625,72.4609375,72.65625,72.8515625,73.046875,73.2421875,73.4375,73.6328125,73.828125,74.0234375,74.21875,74.4140625,74.609375,74.8046875,75.0,75.1953125,75.390625,75.5859375,75.78125,75.9765625,76.171875,76.3671875,76.5625,76.7578125,76.953125,77.1484375,77.34375,77.5390625,77.734375,77.9296875,78.125,78.3203125,78.515625,78.7109375,78.90625,79.1015625,79.296875,79.4921875,79.6875,79.8828125,80.078125,80.2734375,80.46875,80.6640625,80.859375,81.0546875,81.25,81.4453125,81.640625,81.8359375,82.03125,82.2265625,82.421875,82.6171875,82.8125,83.0078125,83.203125,83.3984375,83.59375,83.7890625,83.984375,84.1796875,84.375,84.5703125,84.765625,84.9609375,85.15625,85.3515625,85.546875,85.7421875,85.9375,86.1328125,86.328125,86.5234375,86.71875,86.9140625,87.109375,87.3046875,87.5,87.6953125,87.890625,88.0859375,88.28125,88.4765625,88.671875,88.8671875,89.0625,89.2578125,89.453125,89.6484375,89.84375,90.0390625,90.234375,90.4296875,90.625,90.8203125,91.015625,91.2109375,91.40625,91.6015625,91.796875,91.9921875,92.1875,92.3828125,92.578125,92.7734375,92.96875,93.1640625,93.359375,93.5546875,93.75,93.9453125,94.140625,94.3359375,94.53125,94.7265625,94.921875,95.1171875,95.3125,95.5078125,95.703125,95.8984375,96.09375,96.2890625,96.484375,96.6796875,96.875,97.0703125,97.265625,97.4609375,97.65625,97.8515625,98.046875,98.2421875,98.4375,98.6328125,98.828125,99.0234375,99.21875,99.4140625,99.609375,99.8046875],\"y\":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,532971,35,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,13323154,91712329570,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,806025,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,444268,61,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,29,596600,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},\"selected\":{\"id\":\"30961\"},\"selection_policy\":{\"id\":\"30962\"}},\"id\":\"30946\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"data\":{\"x\":[0.0,0.1953125,0.390625,0.5859375,0.78125,0.9765625,1.171875,1.3671875,1.5625,1.7578125,1.953125,2.1484375,2.34375,2.5390625,2.734375,2.9296875,3.125,3.3203125,3.515625,3.7109375,3.90625,4.1015625,4.296875,4.4921875,4.6875,4.8828125,5.078125,5.2734375,5.46875,5.6640625,5.859375,6.0546875,6.25,6.4453125,6.640625,6.8359375,7.03125,7.2265625,7.421875,7.6171875,7.8125,8.0078125,8.203125,8.3984375,8.59375,8.7890625,8.984375,9.1796875,9.375,9.5703125,9.765625,9.9609375,10.15625,10.3515625,10.546875,10.7421875,10.9375,11.1328125,11.328125,11.5234375,11.71875,11.9140625,12.109375,12.3046875,12.5,12.6953125,12.890625,13.0859375,13.28125,13.4765625,13.671875,13.8671875,14.0625,14.2578125,14.453125,14.6484375,14.84375,15.0390625,15.234375,15.4296875,15.625,15.8203125,16.015625,16.2109375,16.40625,16.6015625,16.796875,16.9921875,17.1875,17.3828125,17.578125,17.7734375,17.96875,18.1640625,18.359375,18.5546875,18.75,18.9453125,19.140625,19.3359375,19.53125,19.7265625,19.921875,20.1171875,20.3125,20.5078125,20.703125,20.8984375,21.09375,21.2890625,21.484375,21.6796875,21.875,22.0703125,22.265625,22.4609375,22.65625,22.8515625,23.046875,23.2421875,23.4375,23.6328125,23.828125,24.0234375,24.21875,24.4140625,24.609375,24.8046875,25.0,25.1953125,25.390625,25.5859375,25.78125,25.9765625,26.171875,26.3671875,26.5625,26.7578125,26.953125,27.1484375,27.34375,27.5390625,27.734375,27.9296875,28.125,28.3203125,28.515625,28.7109375,28.90625,29.1015625,29.296875,29.4921875,29.6875,29.8828125,30.078125,30.2734375,30.46875,30.6640625,30.859375,31.0546875,31.25,31.4453125,31.640625,31.8359375,32.03125,32.2265625,32.421875,32.6171875,32.8125,33.0078125,33.203125,33.3984375,33.59375,33.7890625,33.984375,34.1796875,34.375,34.5703125,34.765625,34.9609375,35.15625,35.3515625,35.546875,35.7421875,35.9375,36.1328125,36.328125,36.5234375,36.71875,36.9140625,37.109375,37.3046875,37.5,37.6953125,37.890625,38.0859375,38.28125,38.4765625,38.671875,38.8671875,39.0625,39.2578125,39.453125,39.6484375,39.84375,40.0390625,40.234375,40.4296875,40.625,40.8203125,41.015625,41.2109375,41.40625,41.6015625,41.796875,41.9921875,42.1875,42.3828125,42.578125,42.7734375,42.96875,43.1640625,43.359375,43.5546875,43.75,43.9453125,44.140625,44.3359375,44.53125,44.7265625,44.921875,45.1171875,45.3125,45.5078125,45.703125,45.8984375,46.09375,46.2890625,46.484375,46.6796875,46.875,47.0703125,47.265625,47.4609375,47.65625,47.8515625,48.046875,48.2421875,48.4375,48.6328125,48.828125,49.0234375,49.21875,49.4140625,49.609375,49.8046875,50.0,50.1953125,50.390625,50.5859375,50.78125,50.9765625,51.171875,51.3671875,51.5625,51.7578125,51.953125,52.1484375,52.34375,52.5390625,52.734375,52.9296875,53.125,53.3203125,53.515625,53.7109375,53.90625,54.1015625,54.296875,54.4921875,54.6875,54.8828125,55.078125,55.2734375,55.46875,55.6640625,55.859375,56.0546875,56.25,56.4453125,56.640625,56.8359375,57.03125,57.2265625,57.421875,57.6171875,57.8125,58.0078125,58.203125,58.3984375,58.59375,58.7890625,58.984375,59.1796875,59.375,59.5703125,59.765625,59.9609375,60.15625,60.3515625,60.546875,60.7421875,60.9375,61.1328125,61.328125,61.5234375,61.71875,61.9140625,62.109375,62.3046875,62.5,62.6953125,62.890625,63.0859375,63.28125,63.4765625,63.671875,63.8671875,64.0625,64.2578125,64.453125,64.6484375,64.84375,65.0390625,65.234375,65.4296875,65.625,65.8203125,66.015625,66.2109375,66.40625,66.6015625,66.796875,66.9921875,67.1875,67.3828125,67.578125,67.7734375,67.96875,68.1640625,68.359375,68.5546875,68.75,68.9453125,69.140625,69.3359375,69.53125,69.7265625,69.921875,70.1171875,70.3125,70.5078125,70.703125,70.8984375,71.09375,71.2890625,71.484375,71.6796875,71.875,72.0703125,72.265625,72.4609375,72.65625,72.8515625,73.046875,73.2421875,73.4375,73.6328125,73.828125,74.0234375,74.21875,74.4140625,74.609375,74.8046875,75.0,75.1953125,75.390625,75.5859375,75.78125,75.9765625,76.171875,76.3671875,76.5625,76.7578125,76.953125,77.1484375,77.34375,77.5390625,77.734375,77.9296875,78.125,78.3203125,78.515625,78.7109375,78.90625,79.1015625,79.296875,79.4921875,79.6875,79.8828125,80.078125,80.2734375,80.46875,80.6640625,80.859375,81.0546875,81.25,81.4453125,81.640625,81.8359375,82.03125,82.2265625,82.421875,82.6171875,82.8125,83.0078125,83.203125,83.3984375,83.59375,83.7890625,83.984375,84.1796875,84.375,84.5703125,84.765625,84.9609375,85.15625,85.3515625,85.546875,85.7421875,85.9375,86.1328125,86.328125,86.5234375,86.71875,86.9140625,87.109375,87.3046875,87.5,87.6953125,87.890625,88.0859375,88.28125,88.4765625,88.671875,88.8671875,89.0625,89.2578125,89.453125,89.6484375,89.84375,90.0390625,90.234375,90.4296875,90.625,90.8203125,91.015625,91.2109375,91.40625,91.6015625,91.796875,91.9921875,92.1875,92.3828125,92.578125,92.7734375,92.96875,93.1640625,93.359375,93.5546875,93.75,93.9453125,94.140625,94.3359375,94.53125,94.7265625,94.921875,95.1171875,95.3125,95.5078125,95.703125,95.8984375,96.09375,96.2890625,96.484375,96.6796875,96.875,97.0703125,97.265625,97.4609375,97.65625,97.8515625,98.046875,98.2421875,98.4375,98.6328125,98.828125,99.0234375,99.21875,99.4140625,99.609375,99.8046875],\"y\":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,532971,35,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,13323154,91712329570,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,806025,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,444268,61,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,29,596600,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},\"selected\":{\"id\":\"31005\"},\"selection_policy\":{\"id\":\"31006\"}},\"id\":\"30986\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"source\":{\"id\":\"30965\"}},\"id\":\"30969\",\"type\":\"CDSView\"},{\"attributes\":{\"data_source\":{\"id\":\"30965\"},\"glyph\":{\"id\":\"30966\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"30967\"},\"view\":{\"id\":\"30969\"}},\"id\":\"30968\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"line_alpha\":0.1,\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"30967\",\"type\":\"Line\"},{\"attributes\":{\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"30966\",\"type\":\"Line\"},{\"attributes\":{\"label\":{\"value\":\"input 96\"},\"renderers\":[{\"id\":\"30931\"}]},\"id\":\"30945\",\"type\":\"LegendItem\"},{\"attributes\":{},\"id\":\"30982\",\"type\":\"Selection\"},{\"attributes\":{},\"id\":\"30941\",\"type\":\"Selection\"},{\"attributes\":{},\"id\":\"30942\",\"type\":\"UnionRenderers\"},{\"attributes\":{},\"id\":\"30983\",\"type\":\"UnionRenderers\"},{\"attributes\":{\"items\":[{\"id\":\"30945\"},{\"id\":\"30964\"},{\"id\":\"30985\"},{\"id\":\"31008\"},{\"id\":\"31033\"},{\"id\":\"31060\"},{\"id\":\"31089\"}]},\"id\":\"30944\",\"type\":\"Legend\"},{\"attributes\":{\"label\":{\"value\":\"input 98\"},\"renderers\":[{\"id\":\"30968\"}]},\"id\":\"30985\",\"type\":\"LegendItem\"},{\"attributes\":{\"data\":{\"x\":[0.0,0.1953125,0.390625,0.5859375,0.78125,0.9765625,1.171875,1.3671875,1.5625,1.7578125,1.953125,2.1484375,2.34375,2.5390625,2.734375,2.9296875,3.125,3.3203125,3.515625,3.7109375,3.90625,4.1015625,4.296875,4.4921875,4.6875,4.8828125,5.078125,5.2734375,5.46875,5.6640625,5.859375,6.0546875,6.25,6.4453125,6.640625,6.8359375,7.03125,7.2265625,7.421875,7.6171875,7.8125,8.0078125,8.203125,8.3984375,8.59375,8.7890625,8.984375,9.1796875,9.375,9.5703125,9.765625,9.9609375,10.15625,10.3515625,10.546875,10.7421875,10.9375,11.1328125,11.328125,11.5234375,11.71875,11.9140625,12.109375,12.3046875,12.5,12.6953125,12.890625,13.0859375,13.28125,13.4765625,13.671875,13.8671875,14.0625,14.2578125,14.453125,14.6484375,14.84375,15.0390625,15.234375,15.4296875,15.625,15.8203125,16.015625,16.2109375,16.40625,16.6015625,16.796875,16.9921875,17.1875,17.3828125,17.578125,17.7734375,17.96875,18.1640625,18.359375,18.5546875,18.75,18.9453125,19.140625,19.3359375,19.53125,19.7265625,19.921875,20.1171875,20.3125,20.5078125,20.703125,20.8984375,21.09375,21.2890625,21.484375,21.6796875,21.875,22.0703125,22.265625,22.4609375,22.65625,22.8515625,23.046875,23.2421875,23.4375,23.6328125,23.828125,24.0234375,24.21875,24.4140625,24.609375,24.8046875,25.0,25.1953125,25.390625,25.5859375,25.78125,25.9765625,26.171875,26.3671875,26.5625,26.7578125,26.953125,27.1484375,27.34375,27.5390625,27.734375,27.9296875,28.125,28.3203125,28.515625,28.7109375,28.90625,29.1015625,29.296875,29.4921875,29.6875,29.8828125,30.078125,30.2734375,30.46875,30.6640625,30.859375,31.0546875,31.25,31.4453125,31.640625,31.8359375,32.03125,32.2265625,32.421875,32.6171875,32.8125,33.0078125,33.203125,33.3984375,33.59375,33.7890625,33.984375,34.1796875,34.375,34.5703125,34.765625,34.9609375,35.15625,35.3515625,35.546875,35.7421875,35.9375,36.1328125,36.328125,36.5234375,36.71875,36.9140625,37.109375,37.3046875,37.5,37.6953125,37.890625,38.0859375,38.28125,38.4765625,38.671875,38.8671875,39.0625,39.2578125,39.453125,39.6484375,39.84375,40.0390625,40.234375,40.4296875,40.625,40.8203125,41.015625,41.2109375,41.40625,41.6015625,41.796875,41.9921875,42.1875,42.3828125,42.578125,42.7734375,42.96875,43.1640625,43.359375,43.5546875,43.75,43.9453125,44.140625,44.3359375,44.53125,44.7265625,44.921875,45.1171875,45.3125,45.5078125,45.703125,45.8984375,46.09375,46.2890625,46.484375,46.6796875,46.875,47.0703125,47.265625,47.4609375,47.65625,47.8515625,48.046875,48.2421875,48.4375,48.6328125,48.828125,49.0234375,49.21875,49.4140625,49.609375,49.8046875,50.0,50.1953125,50.390625,50.5859375,50.78125,50.9765625,51.171875,51.3671875,51.5625,51.7578125,51.953125,52.1484375,52.34375,52.5390625,52.734375,52.9296875,53.125,53.3203125,53.515625,53.7109375,53.90625,54.1015625,54.296875,54.4921875,54.6875,54.8828125,55.078125,55.2734375,55.46875,55.6640625,55.859375,56.0546875,56.25,56.4453125,56.640625,56.8359375,57.03125,57.2265625,57.421875,57.6171875,57.8125,58.0078125,58.203125,58.3984375,58.59375,58.7890625,58.984375,59.1796875,59.375,59.5703125,59.765625,59.9609375,60.15625,60.3515625,60.546875,60.7421875,60.9375,61.1328125,61.328125,61.5234375,61.71875,61.9140625,62.109375,62.3046875,62.5,62.6953125,62.890625,63.0859375,63.28125,63.4765625,63.671875,63.8671875,64.0625,64.2578125,64.453125,64.6484375,64.84375,65.0390625,65.234375,65.4296875,65.625,65.8203125,66.015625,66.2109375,66.40625,66.6015625,66.796875,66.9921875,67.1875,67.3828125,67.578125,67.7734375,67.96875,68.1640625,68.359375,68.5546875,68.75,68.9453125,69.140625,69.3359375,69.53125,69.7265625,69.921875,70.1171875,70.3125,70.5078125,70.703125,70.8984375,71.09375,71.2890625,71.484375,71.6796875,71.875,72.0703125,72.265625,72.4609375,72.65625,72.8515625,73.046875,73.2421875,73.4375,73.6328125,73.828125,74.0234375,74.21875,74.4140625,74.609375,74.8046875,75.0,75.1953125,75.390625,75.5859375,75.78125,75.9765625,76.171875,76.3671875,76.5625,76.7578125,76.953125,77.1484375,77.34375,77.5390625,77.734375,77.9296875,78.125,78.3203125,78.515625,78.7109375,78.90625,79.1015625,79.296875,79.4921875,79.6875,79.8828125,80.078125,80.2734375,80.46875,80.6640625,80.859375,81.0546875,81.25,81.4453125,81.640625,81.8359375,82.03125,82.2265625,82.421875,82.6171875,82.8125,83.0078125,83.203125,83.3984375,83.59375,83.7890625,83.984375,84.1796875,84.375,84.5703125,84.765625,84.9609375,85.15625,85.3515625,85.546875,85.7421875,85.9375,86.1328125,86.328125,86.5234375,86.71875,86.9140625,87.109375,87.3046875,87.5,87.6953125,87.890625,88.0859375,88.28125,88.4765625,88.671875,88.8671875,89.0625,89.2578125,89.453125,89.6484375,89.84375,90.0390625,90.234375,90.4296875,90.625,90.8203125,91.015625,91.2109375,91.40625,91.6015625,91.796875,91.9921875,92.1875,92.3828125,92.578125,92.7734375,92.96875,93.1640625,93.359375,93.5546875,93.75,93.9453125,94.140625,94.3359375,94.53125,94.7265625,94.921875,95.1171875,95.3125,95.5078125,95.703125,95.8984375,96.09375,96.2890625,96.484375,96.6796875,96.875,97.0703125,97.265625,97.4609375,97.65625,97.8515625,98.046875,98.2421875,98.4375,98.6328125,98.828125,99.0234375,99.21875,99.4140625,99.609375,99.8046875],\"y\":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,532157,25,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,13313912,91696270071,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,806025,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,437811,61,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,29,585376,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},\"selected\":{\"id\":\"31030\"},\"selection_policy\":{\"id\":\"31031\"}},\"id\":\"31009\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"source\":{\"id\":\"30986\"}},\"id\":\"30990\",\"type\":\"CDSView\"},{\"attributes\":{\"data_source\":{\"id\":\"30986\"},\"glyph\":{\"id\":\"30987\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"30988\"},\"view\":{\"id\":\"30990\"}},\"id\":\"30989\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"line_alpha\":0.1,\"line_color\":\"pink\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"30988\",\"type\":\"Line\"},{\"attributes\":{\"line_color\":\"pink\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"30987\",\"type\":\"Line\"},{\"attributes\":{},\"id\":\"30900\",\"type\":\"DataRange1d\"},{\"attributes\":{},\"id\":\"31005\",\"type\":\"Selection\"},{\"attributes\":{},\"id\":\"31006\",\"type\":\"UnionRenderers\"},{\"attributes\":{\"source\":{\"id\":\"30946\"}},\"id\":\"30950\",\"type\":\"CDSView\"},{\"attributes\":{\"label\":{\"value\":\"input 99\"},\"renderers\":[{\"id\":\"30989\"}]},\"id\":\"31008\",\"type\":\"LegendItem\"},{\"attributes\":{\"data\":{\"x\":[0.0,0.1953125,0.390625,0.5859375,0.78125,0.9765625,1.171875,1.3671875,1.5625,1.7578125,1.953125,2.1484375,2.34375,2.5390625,2.734375,2.9296875,3.125,3.3203125,3.515625,3.7109375,3.90625,4.1015625,4.296875,4.4921875,4.6875,4.8828125,5.078125,5.2734375,5.46875,5.6640625,5.859375,6.0546875,6.25,6.4453125,6.640625,6.8359375,7.03125,7.2265625,7.421875,7.6171875,7.8125,8.0078125,8.203125,8.3984375,8.59375,8.7890625,8.984375,9.1796875,9.375,9.5703125,9.765625,9.9609375,10.15625,10.3515625,10.546875,10.7421875,10.9375,11.1328125,11.328125,11.5234375,11.71875,11.9140625,12.109375,12.3046875,12.5,12.6953125,12.890625,13.0859375,13.28125,13.4765625,13.671875,13.8671875,14.0625,14.2578125,14.453125,14.6484375,14.84375,15.0390625,15.234375,15.4296875,15.625,15.8203125,16.015625,16.2109375,16.40625,16.6015625,16.796875,16.9921875,17.1875,17.3828125,17.578125,17.7734375,17.96875,18.1640625,18.359375,18.5546875,18.75,18.9453125,19.140625,19.3359375,19.53125,19.7265625,19.921875,20.1171875,20.3125,20.5078125,20.703125,20.8984375,21.09375,21.2890625,21.484375,21.6796875,21.875,22.0703125,22.265625,22.4609375,22.65625,22.8515625,23.046875,23.2421875,23.4375,23.6328125,23.828125,24.0234375,24.21875,24.4140625,24.609375,24.8046875,25.0,25.1953125,25.390625,25.5859375,25.78125,25.9765625,26.171875,26.3671875,26.5625,26.7578125,26.953125,27.1484375,27.34375,27.5390625,27.734375,27.9296875,28.125,28.3203125,28.515625,28.7109375,28.90625,29.1015625,29.296875,29.4921875,29.6875,29.8828125,30.078125,30.2734375,30.46875,30.6640625,30.859375,31.0546875,31.25,31.4453125,31.640625,31.8359375,32.03125,32.2265625,32.421875,32.6171875,32.8125,33.0078125,33.203125,33.3984375,33.59375,33.7890625,33.984375,34.1796875,34.375,34.5703125,34.765625,34.9609375,35.15625,35.3515625,35.546875,35.7421875,35.9375,36.1328125,36.328125,36.5234375,36.71875,36.9140625,37.109375,37.3046875,37.5,37.6953125,37.890625,38.0859375,38.28125,38.4765625,38.671875,38.8671875,39.0625,39.2578125,39.453125,39.6484375,39.84375,40.0390625,40.234375,40.4296875,40.625,40.8203125,41.015625,41.2109375,41.40625,41.6015625,41.796875,41.9921875,42.1875,42.3828125,42.578125,42.7734375,42.96875,43.1640625,43.359375,43.5546875,43.75,43.9453125,44.140625,44.3359375,44.53125,44.7265625,44.921875,45.1171875,45.3125,45.5078125,45.703125,45.8984375,46.09375,46.2890625,46.484375,46.6796875,46.875,47.0703125,47.265625,47.4609375,47.65625,47.8515625,48.046875,48.2421875,48.4375,48.6328125,48.828125,49.0234375,49.21875,49.4140625,49.609375,49.8046875,50.0,50.1953125,50.390625,50.5859375,50.78125,50.9765625,51.171875,51.3671875,51.5625,51.7578125,51.953125,52.1484375,52.34375,52.5390625,52.734375,52.9296875,53.125,53.3203125,53.515625,53.7109375,53.90625,54.1015625,54.296875,54.4921875,54.6875,54.8828125,55.078125,55.2734375,55.46875,55.6640625,55.859375,56.0546875,56.25,56.4453125,56.640625,56.8359375,57.03125,57.2265625,57.421875,57.6171875,57.8125,58.0078125,58.203125,58.3984375,58.59375,58.7890625,58.984375,59.1796875,59.375,59.5703125,59.765625,59.9609375,60.15625,60.3515625,60.546875,60.7421875,60.9375,61.1328125,61.328125,61.5234375,61.71875,61.9140625,62.109375,62.3046875,62.5,62.6953125,62.890625,63.0859375,63.28125,63.4765625,63.671875,63.8671875,64.0625,64.2578125,64.453125,64.6484375,64.84375,65.0390625,65.234375,65.4296875,65.625,65.8203125,66.015625,66.2109375,66.40625,66.6015625,66.796875,66.9921875,67.1875,67.3828125,67.578125,67.7734375,67.96875,68.1640625,68.359375,68.5546875,68.75,68.9453125,69.140625,69.3359375,69.53125,69.7265625,69.921875,70.1171875,70.3125,70.5078125,70.703125,70.8984375,71.09375,71.2890625,71.484375,71.6796875,71.875,72.0703125,72.265625,72.4609375,72.65625,72.8515625,73.046875,73.2421875,73.4375,73.6328125,73.828125,74.0234375,74.21875,74.4140625,74.609375,74.8046875,75.0,75.1953125,75.390625,75.5859375,75.78125,75.9765625,76.171875,76.3671875,76.5625,76.7578125,76.953125,77.1484375,77.34375,77.5390625,77.734375,77.9296875,78.125,78.3203125,78.515625,78.7109375,78.90625,79.1015625,79.296875,79.4921875,79.6875,79.8828125,80.078125,80.2734375,80.46875,80.6640625,80.859375,81.0546875,81.25,81.4453125,81.640625,81.8359375,82.03125,82.2265625,82.421875,82.6171875,82.8125,83.0078125,83.203125,83.3984375,83.59375,83.7890625,83.984375,84.1796875,84.375,84.5703125,84.765625,84.9609375,85.15625,85.3515625,85.546875,85.7421875,85.9375,86.1328125,86.328125,86.5234375,86.71875,86.9140625,87.109375,87.3046875,87.5,87.6953125,87.890625,88.0859375,88.28125,88.4765625,88.671875,88.8671875,89.0625,89.2578125,89.453125,89.6484375,89.84375,90.0390625,90.234375,90.4296875,90.625,90.8203125,91.015625,91.2109375,91.40625,91.6015625,91.796875,91.9921875,92.1875,92.3828125,92.578125,92.7734375,92.96875,93.1640625,93.359375,93.5546875,93.75,93.9453125,94.140625,94.3359375,94.53125,94.7265625,94.921875,95.1171875,95.3125,95.5078125,95.703125,95.8984375,96.09375,96.2890625,96.484375,96.6796875,96.875,97.0703125,97.265625,97.4609375,97.65625,97.8515625,98.046875,98.2421875,98.4375,98.6328125,98.828125,99.0234375,99.21875,99.4140625,99.609375,99.8046875],\"y\":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,532971,35,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,13323154,91712329570,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,806025,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,444268,61,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,29,596600,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},\"selected\":{\"id\":\"31057\"},\"selection_policy\":{\"id\":\"31058\"}},\"id\":\"31034\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"source\":{\"id\":\"31009\"}},\"id\":\"31013\",\"type\":\"CDSView\"},{\"attributes\":{\"data_source\":{\"id\":\"31009\"},\"glyph\":{\"id\":\"31010\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"31011\"},\"view\":{\"id\":\"31013\"}},\"id\":\"31012\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"line_alpha\":0.1,\"line_color\":\"darkblue\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"31011\",\"type\":\"Line\"},{\"attributes\":{\"line_color\":\"darkblue\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"31010\",\"type\":\"Line\"},{\"attributes\":{\"data_source\":{\"id\":\"30946\"},\"glyph\":{\"id\":\"30947\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"30948\"},\"view\":{\"id\":\"30950\"}},\"id\":\"30949\",\"type\":\"GlyphRenderer\"},{\"attributes\":{},\"id\":\"30898\",\"type\":\"DataRange1d\"},{\"attributes\":{},\"id\":\"31030\",\"type\":\"Selection\"},{\"attributes\":{},\"id\":\"31031\",\"type\":\"UnionRenderers\"},{\"attributes\":{\"label\":{\"value\":\"input 100\"},\"renderers\":[{\"id\":\"31012\"}]},\"id\":\"31033\",\"type\":\"LegendItem\"},{\"attributes\":{\"data\":{\"x\":[0.0,0.1953125,0.390625,0.5859375,0.78125,0.9765625,1.171875,1.3671875,1.5625,1.7578125,1.953125,2.1484375,2.34375,2.5390625,2.734375,2.9296875,3.125,3.3203125,3.515625,3.7109375,3.90625,4.1015625,4.296875,4.4921875,4.6875,4.8828125,5.078125,5.2734375,5.46875,5.6640625,5.859375,6.0546875,6.25,6.4453125,6.640625,6.8359375,7.03125,7.2265625,7.421875,7.6171875,7.8125,8.0078125,8.203125,8.3984375,8.59375,8.7890625,8.984375,9.1796875,9.375,9.5703125,9.765625,9.9609375,10.15625,10.3515625,10.546875,10.7421875,10.9375,11.1328125,11.328125,11.5234375,11.71875,11.9140625,12.109375,12.3046875,12.5,12.6953125,12.890625,13.0859375,13.28125,13.4765625,13.671875,13.8671875,14.0625,14.2578125,14.453125,14.6484375,14.84375,15.0390625,15.234375,15.4296875,15.625,15.8203125,16.015625,16.2109375,16.40625,16.6015625,16.796875,16.9921875,17.1875,17.3828125,17.578125,17.7734375,17.96875,18.1640625,18.359375,18.5546875,18.75,18.9453125,19.140625,19.3359375,19.53125,19.7265625,19.921875,20.1171875,20.3125,20.5078125,20.703125,20.8984375,21.09375,21.2890625,21.484375,21.6796875,21.875,22.0703125,22.265625,22.4609375,22.65625,22.8515625,23.046875,23.2421875,23.4375,23.6328125,23.828125,24.0234375,24.21875,24.4140625,24.609375,24.8046875,25.0,25.1953125,25.390625,25.5859375,25.78125,25.9765625,26.171875,26.3671875,26.5625,26.7578125,26.953125,27.1484375,27.34375,27.5390625,27.734375,27.9296875,28.125,28.3203125,28.515625,28.7109375,28.90625,29.1015625,29.296875,29.4921875,29.6875,29.8828125,30.078125,30.2734375,30.46875,30.6640625,30.859375,31.0546875,31.25,31.4453125,31.640625,31.8359375,32.03125,32.2265625,32.421875,32.6171875,32.8125,33.0078125,33.203125,33.3984375,33.59375,33.7890625,33.984375,34.1796875,34.375,34.5703125,34.765625,34.9609375,35.15625,35.3515625,35.546875,35.7421875,35.9375,36.1328125,36.328125,36.5234375,36.71875,36.9140625,37.109375,37.3046875,37.5,37.6953125,37.890625,38.0859375,38.28125,38.4765625,38.671875,38.8671875,39.0625,39.2578125,39.453125,39.6484375,39.84375,40.0390625,40.234375,40.4296875,40.625,40.8203125,41.015625,41.2109375,41.40625,41.6015625,41.796875,41.9921875,42.1875,42.3828125,42.578125,42.7734375,42.96875,43.1640625,43.359375,43.5546875,43.75,43.9453125,44.140625,44.3359375,44.53125,44.7265625,44.921875,45.1171875,45.3125,45.5078125,45.703125,45.8984375,46.09375,46.2890625,46.484375,46.6796875,46.875,47.0703125,47.265625,47.4609375,47.65625,47.8515625,48.046875,48.2421875,48.4375,48.6328125,48.828125,49.0234375,49.21875,49.4140625,49.609375,49.8046875,50.0,50.1953125,50.390625,50.5859375,50.78125,50.9765625,51.171875,51.3671875,51.5625,51.7578125,51.953125,52.1484375,52.34375,52.5390625,52.734375,52.9296875,53.125,53.3203125,53.515625,53.7109375,53.90625,54.1015625,54.296875,54.4921875,54.6875,54.8828125,55.078125,55.2734375,55.46875,55.6640625,55.859375,56.0546875,56.25,56.4453125,56.640625,56.8359375,57.03125,57.2265625,57.421875,57.6171875,57.8125,58.0078125,58.203125,58.3984375,58.59375,58.7890625,58.984375,59.1796875,59.375,59.5703125,59.765625,59.9609375,60.15625,60.3515625,60.546875,60.7421875,60.9375,61.1328125,61.328125,61.5234375,61.71875,61.9140625,62.109375,62.3046875,62.5,62.6953125,62.890625,63.0859375,63.28125,63.4765625,63.671875,63.8671875,64.0625,64.2578125,64.453125,64.6484375,64.84375,65.0390625,65.234375,65.4296875,65.625,65.8203125,66.015625,66.2109375,66.40625,66.6015625,66.796875,66.9921875,67.1875,67.3828125,67.578125,67.7734375,67.96875,68.1640625,68.359375,68.5546875,68.75,68.9453125,69.140625,69.3359375,69.53125,69.7265625,69.921875,70.1171875,70.3125,70.5078125,70.703125,70.8984375,71.09375,71.2890625,71.484375,71.6796875,71.875,72.0703125,72.265625,72.4609375,72.65625,72.8515625,73.046875,73.2421875,73.4375,73.6328125,73.828125,74.0234375,74.21875,74.4140625,74.609375,74.8046875,75.0,75.1953125,75.390625,75.5859375,75.78125,75.9765625,76.171875,76.3671875,76.5625,76.7578125,76.953125,77.1484375,77.34375,77.5390625,77.734375,77.9296875,78.125,78.3203125,78.515625,78.7109375,78.90625,79.1015625,79.296875,79.4921875,79.6875,79.8828125,80.078125,80.2734375,80.46875,80.6640625,80.859375,81.0546875,81.25,81.4453125,81.640625,81.8359375,82.03125,82.2265625,82.421875,82.6171875,82.8125,83.0078125,83.203125,83.3984375,83.59375,83.7890625,83.984375,84.1796875,84.375,84.5703125,84.765625,84.9609375,85.15625,85.3515625,85.546875,85.7421875,85.9375,86.1328125,86.328125,86.5234375,86.71875,86.9140625,87.109375,87.3046875,87.5,87.6953125,87.890625,88.0859375,88.28125,88.4765625,88.671875,88.8671875,89.0625,89.2578125,89.453125,89.6484375,89.84375,90.0390625,90.234375,90.4296875,90.625,90.8203125,91.015625,91.2109375,91.40625,91.6015625,91.796875,91.9921875,92.1875,92.3828125,92.578125,92.7734375,92.96875,93.1640625,93.359375,93.5546875,93.75,93.9453125,94.140625,94.3359375,94.53125,94.7265625,94.921875,95.1171875,95.3125,95.5078125,95.703125,95.8984375,96.09375,96.2890625,96.484375,96.6796875,96.875,97.0703125,97.265625,97.4609375,97.65625,97.8515625,98.046875,98.2421875,98.4375,98.6328125,98.828125,99.0234375,99.21875,99.4140625,99.609375,99.8046875],\"y\":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,532157,25,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,13313912,91696270071,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,806025,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,437811,61,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,29,585376,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},\"selected\":{\"id\":\"31086\"},\"selection_policy\":{\"id\":\"31087\"}},\"id\":\"31061\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"source\":{\"id\":\"31034\"}},\"id\":\"31038\",\"type\":\"CDSView\"},{\"attributes\":{\"data_source\":{\"id\":\"31034\"},\"glyph\":{\"id\":\"31035\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"31036\"},\"view\":{\"id\":\"31038\"}},\"id\":\"31037\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"line_alpha\":0.1,\"line_color\":\"green\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"31036\",\"type\":\"Line\"},{\"attributes\":{\"line_color\":\"green\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"31035\",\"type\":\"Line\"},{\"attributes\":{},\"id\":\"31057\",\"type\":\"Selection\"},{\"attributes\":{},\"id\":\"30902\",\"type\":\"LinearScale\"},{\"attributes\":{},\"id\":\"31058\",\"type\":\"UnionRenderers\"},{\"attributes\":{\"label\":{\"value\":\"input 101\"},\"renderers\":[{\"id\":\"31037\"}]},\"id\":\"31060\",\"type\":\"LegendItem\"},{\"attributes\":{\"line_alpha\":0.1,\"line_color\":\"cyan\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"30948\",\"type\":\"Line\"},{\"attributes\":{\"line_color\":\"cyan\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"30947\",\"type\":\"Line\"},{\"attributes\":{\"source\":{\"id\":\"31061\"}},\"id\":\"31065\",\"type\":\"CDSView\"},{\"attributes\":{\"data_source\":{\"id\":\"31061\"},\"glyph\":{\"id\":\"31062\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"31063\"},\"view\":{\"id\":\"31065\"}},\"id\":\"31064\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"line_alpha\":0.1,\"line_color\":\"magenta\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"31063\",\"type\":\"Line\"},{\"attributes\":{\"line_color\":\"magenta\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"31062\",\"type\":\"Line\"},{\"attributes\":{},\"id\":\"31086\",\"type\":\"Selection\"},{\"attributes\":{\"line_color\":\"brown\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"30929\",\"type\":\"Line\"},{\"attributes\":{},\"id\":\"31087\",\"type\":\"UnionRenderers\"},{\"attributes\":{\"label\":{\"value\":\"input 102\"},\"renderers\":[{\"id\":\"31064\"}]},\"id\":\"31089\",\"type\":\"LegendItem\"},{\"attributes\":{\"data\":{\"x\":[0.0,0.1953125,0.390625,0.5859375,0.78125,0.9765625,1.171875,1.3671875,1.5625,1.7578125,1.953125,2.1484375,2.34375,2.5390625,2.734375,2.9296875,3.125,3.3203125,3.515625,3.7109375,3.90625,4.1015625,4.296875,4.4921875,4.6875,4.8828125,5.078125,5.2734375,5.46875,5.6640625,5.859375,6.0546875,6.25,6.4453125,6.640625,6.8359375,7.03125,7.2265625,7.421875,7.6171875,7.8125,8.0078125,8.203125,8.3984375,8.59375,8.7890625,8.984375,9.1796875,9.375,9.5703125,9.765625,9.9609375,10.15625,10.3515625,10.546875,10.7421875,10.9375,11.1328125,11.328125,11.5234375,11.71875,11.9140625,12.109375,12.3046875,12.5,12.6953125,12.890625,13.0859375,13.28125,13.4765625,13.671875,13.8671875,14.0625,14.2578125,14.453125,14.6484375,14.84375,15.0390625,15.234375,15.4296875,15.625,15.8203125,16.015625,16.2109375,16.40625,16.6015625,16.796875,16.9921875,17.1875,17.3828125,17.578125,17.7734375,17.96875,18.1640625,18.359375,18.5546875,18.75,18.9453125,19.140625,19.3359375,19.53125,19.7265625,19.921875,20.1171875,20.3125,20.5078125,20.703125,20.8984375,21.09375,21.2890625,21.484375,21.6796875,21.875,22.0703125,22.265625,22.4609375,22.65625,22.8515625,23.046875,23.2421875,23.4375,23.6328125,23.828125,24.0234375,24.21875,24.4140625,24.609375,24.8046875,25.0,25.1953125,25.390625,25.5859375,25.78125,25.9765625,26.171875,26.3671875,26.5625,26.7578125,26.953125,27.1484375,27.34375,27.5390625,27.734375,27.9296875,28.125,28.3203125,28.515625,28.7109375,28.90625,29.1015625,29.296875,29.4921875,29.6875,29.8828125,30.078125,30.2734375,30.46875,30.6640625,30.859375,31.0546875,31.25,31.4453125,31.640625,31.8359375,32.03125,32.2265625,32.421875,32.6171875,32.8125,33.0078125,33.203125,33.3984375,33.59375,33.7890625,33.984375,34.1796875,34.375,34.5703125,34.765625,34.9609375,35.15625,35.3515625,35.546875,35.7421875,35.9375,36.1328125,36.328125,36.5234375,36.71875,36.9140625,37.109375,37.3046875,37.5,37.6953125,37.890625,38.0859375,38.28125,38.4765625,38.671875,38.8671875,39.0625,39.2578125,39.453125,39.6484375,39.84375,40.0390625,40.234375,40.4296875,40.625,40.8203125,41.015625,41.2109375,41.40625,41.6015625,41.796875,41.9921875,42.1875,42.3828125,42.578125,42.7734375,42.96875,43.1640625,43.359375,43.5546875,43.75,43.9453125,44.140625,44.3359375,44.53125,44.7265625,44.921875,45.1171875,45.3125,45.5078125,45.703125,45.8984375,46.09375,46.2890625,46.484375,46.6796875,46.875,47.0703125,47.265625,47.4609375,47.65625,47.8515625,48.046875,48.2421875,48.4375,48.6328125,48.828125,49.0234375,49.21875,49.4140625,49.609375,49.8046875,50.0,50.1953125,50.390625,50.5859375,50.78125,50.9765625,51.171875,51.3671875,51.5625,51.7578125,51.953125,52.1484375,52.34375,52.5390625,52.734375,52.9296875,53.125,53.3203125,53.515625,53.7109375,53.90625,54.1015625,54.296875,54.4921875,54.6875,54.8828125,55.078125,55.2734375,55.46875,55.6640625,55.859375,56.0546875,56.25,56.4453125,56.640625,56.8359375,57.03125,57.2265625,57.421875,57.6171875,57.8125,58.0078125,58.203125,58.3984375,58.59375,58.7890625,58.984375,59.1796875,59.375,59.5703125,59.765625,59.9609375,60.15625,60.3515625,60.546875,60.7421875,60.9375,61.1328125,61.328125,61.5234375,61.71875,61.9140625,62.109375,62.3046875,62.5,62.6953125,62.890625,63.0859375,63.28125,63.4765625,63.671875,63.8671875,64.0625,64.2578125,64.453125,64.6484375,64.84375,65.0390625,65.234375,65.4296875,65.625,65.8203125,66.015625,66.2109375,66.40625,66.6015625,66.796875,66.9921875,67.1875,67.3828125,67.578125,67.7734375,67.96875,68.1640625,68.359375,68.5546875,68.75,68.9453125,69.140625,69.3359375,69.53125,69.7265625,69.921875,70.1171875,70.3125,70.5078125,70.703125,70.8984375,71.09375,71.2890625,71.484375,71.6796875,71.875,72.0703125,72.265625,72.4609375,72.65625,72.8515625,73.046875,73.2421875,73.4375,73.6328125,73.828125,74.0234375,74.21875,74.4140625,74.609375,74.8046875,75.0,75.1953125,75.390625,75.5859375,75.78125,75.9765625,76.171875,76.3671875,76.5625,76.7578125,76.953125,77.1484375,77.34375,77.5390625,77.734375,77.9296875,78.125,78.3203125,78.515625,78.7109375,78.90625,79.1015625,79.296875,79.4921875,79.6875,79.8828125,80.078125,80.2734375,80.46875,80.6640625,80.859375,81.0546875,81.25,81.4453125,81.640625,81.8359375,82.03125,82.2265625,82.421875,82.6171875,82.8125,83.0078125,83.203125,83.3984375,83.59375,83.7890625,83.984375,84.1796875,84.375,84.5703125,84.765625,84.9609375,85.15625,85.3515625,85.546875,85.7421875,85.9375,86.1328125,86.328125,86.5234375,86.71875,86.9140625,87.109375,87.3046875,87.5,87.6953125,87.890625,88.0859375,88.28125,88.4765625,88.671875,88.8671875,89.0625,89.2578125,89.453125,89.6484375,89.84375,90.0390625,90.234375,90.4296875,90.625,90.8203125,91.015625,91.2109375,91.40625,91.6015625,91.796875,91.9921875,92.1875,92.3828125,92.578125,92.7734375,92.96875,93.1640625,93.359375,93.5546875,93.75,93.9453125,94.140625,94.3359375,94.53125,94.7265625,94.921875,95.1171875,95.3125,95.5078125,95.703125,95.8984375,96.09375,96.2890625,96.484375,96.6796875,96.875,97.0703125,97.265625,97.4609375,97.65625,97.8515625,98.046875,98.2421875,98.4375,98.6328125,98.828125,99.0234375,99.21875,99.4140625,99.609375,99.8046875],\"y\":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,532971,35,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,13323154,91712329570,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,806025,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,444268,61,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,29,596600,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},\"selected\":{\"id\":\"30941\"},\"selection_policy\":{\"id\":\"30942\"}},\"id\":\"30928\",\"type\":\"ColumnDataSource\"}],\"root_ids\":[\"30897\"]},\"title\":\"Bokeh Application\",\"version\":\"2.3.3\"}};\n", + " var render_items = [{\"docid\":\"665d586c-411a-47fc-8844-15f332d3038a\",\"root_ids\":[\"30897\"],\"roots\":{\"30897\":\"732e77fe-65b7-4633-9104-3bae40cc4886\"}}];\n", + " root.Bokeh.embed.embed_items_notebook(docs_json, render_items);\n", + "\n", + " }\n", + " if (root.Bokeh !== undefined) {\n", + " embed_document(root);\n", + " } else {\n", + " var attempts = 0;\n", + " var timer = setInterval(function(root) {\n", + " if (root.Bokeh !== undefined) {\n", + " clearInterval(timer);\n", + " embed_document(root);\n", + " } else {\n", + " attempts++;\n", + " if (attempts > 100) {\n", + " clearInterval(timer);\n", + " console.log(\"Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing\");\n", + " }\n", + " }\n", + " }, 10, root)\n", + " }\n", + "})(window);" + ], + "application/vnd.bokehjs_exec.v0+json": "" + }, + "metadata": { + "application/vnd.bokehjs_exec.v0+json": { + "id": "30897" + } + }, + "output_type": "display_data" + } + ], + "source": [ + "from bokeh.plotting import figure \n", + "from bokeh.io import output_notebook, show\n", + "p = figure(width=1000, height=500, y_axis_type=\"log\", x_axis_label=\"MHz\", y_axis_label=\"power\")\n", + "x=[s/512*100 for s in range(d.sst_r.shape[1])]\n", + "colors=['darkblue','green','magenta','orange','blue','red','brown','cyan','black','pink']\n", + "for ant in range(96,103):\n", + " p.line(x, d.sst_r[ant]+1,line_color=colors[ant % len(colors)],legend_label=\"input %s\" % ant)\n", + "output_notebook()\n", + "show(p)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71d8ef37", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "StationControl", + "language": "python", + "name": "stationcontrol" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/jupyter-notebooks/archiving_demo.ipynb b/jupyter-notebooks/archiving_demo.ipynb index 28eeb7d3196ea347f817c3d20ee8683d096ad2bd..7c63a8b215910525ae730d4a0521605eb57347c2 100644 --- a/jupyter-notebooks/archiving_demo.ipynb +++ b/jupyter-notebooks/archiving_demo.ipynb @@ -33,7 +33,7 @@ } ], "source": [ - "dev_rand = DeviceProxy(\"LTS/Random_Data/1\")\n", + "dev_rand = DeviceProxy(\"STAT/Random_Data/1\")\n", "dev_rand.get_attribute_list()" ] }, @@ -44,7 +44,7 @@ "metadata": {}, "outputs": [], "source": [ - "attr_fullname = 'lts/random_data/1/rnd4'\n", + "attr_fullname = 'stat/random_data/1/rnd4'\n", "archiver = Archiver()" ] }, @@ -155,7 +155,7 @@ "metadata": {}, "outputs": [], "source": [ - "attr_fullname = 'lts/random_data/1/rnd21'\n", + "attr_fullname = 'stat/random_data/1/rnd21'\n", "archiver.add_attribute_to_archiver(attr_fullname,polling_period=1000,event_period=1000)" ] }, @@ -1243,7 +1243,7 @@ "metadata": {}, "outputs": [], "source": [ - "d=DeviceProxy(\"LTS/PCC/1\")" + "d=DeviceProxy(\"STAT/RECV/1\")" ] }, { @@ -1307,7 +1307,7 @@ "tango://databaseds:10000/lts/randomdata/1/rnd21\n", "tango://databaseds:10000/lts/random_data/1/rnd1\n", "tango://databaseds:10000/lts/random_data/1/rnd21\n", - "tango://databaseds:10000/lts/pcc/1/rcu_temperature_r\n", + "tango://databaseds:10000/lts/recv/1/rcu_temperature_r\n", "tango://databaseds:10000/lts/random_data/1/rnd3\n", "tango://databaseds:10000/lts/random_data/1/rnd2\n", "tango://databaseds:10000/lts/random_data/1/rnd4\n" @@ -1328,7 +1328,7 @@ "metadata": {}, "outputs": [], "source": [ - "main_att = 'lts/pcc/1/RCU_temperature_R'\n", + "main_att = 'stat/recv/1/RCU_temperature_R'\n", "archiver.add_attribute_to_archiver(main_att,polling_period=1000,event_period=1000)" ] }, @@ -1981,4 +1981,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/jupyter-notebooks/ini_device.ipynb b/jupyter-notebooks/ini_device.ipynb index ba365f263ca35e627b0430f26a02d53af059333a..a5b5e950e9cdf5dbffa58624523df2a7cca6b17f 100644 --- a/jupyter-notebooks/ini_device.ipynb +++ b/jupyter-notebooks/ini_device.ipynb @@ -18,7 +18,7 @@ "metadata": {}, "outputs": [], "source": [ - "d=DeviceProxy(\"LTS/ini_device/1\")" + "d=DeviceProxy(\"STAT/ini_device/1\")" ] }, { @@ -235,4 +235,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/jupyter-notebooks/test_device.ipynb b/jupyter-notebooks/test_device.ipynb index 7701f520937bcaf88b09f624c8b9e3a4ee752c85..3a718f2cc744d99d9cc7c96e295aaf30b3a371ca 100644 --- a/jupyter-notebooks/test_device.ipynb +++ b/jupyter-notebooks/test_device.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 61, + "execution_count": 15, "id": "waiting-chance", "metadata": {}, "outputs": [], @@ -13,17 +13,17 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 16, "id": "moving-alexandria", "metadata": {}, "outputs": [], "source": [ - "d=DeviceProxy(\"LTS/SST/1\")" + "d=DeviceProxy(\"STAT/SST/1\")" ] }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 17, "id": "ranking-aluminum", "metadata": {}, "outputs": [ @@ -46,12 +46,14 @@ " d.on()\n", "state = str(d.state())\n", "if state == \"ON\":\n", - " print(\"Device is now in on state\")\n" + " print(\"Device is now in on state\")\n", + "else:\n", + " print(f\"Device is now in {state} state\")" ] }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 18, "id": "beneficial-evidence", "metadata": {}, "outputs": [ @@ -59,64 +61,107 @@ "name": "stdout", "output_type": "stream", "text": [ - "packet_count_R [55]\n", - "last_packet_timestamp_R [1623249385]\n", - "queue_percentage_used_R [0.]\n", - "State <function __get_command_func.<locals>.f at 0x7fcb205fd0d0>\n", - "Status <function __get_command_func.<locals>.f at 0x7fcb205fd0d0>\n" + "['version_R', 'opcua_missing_attributes_R', 'UNB2TR_translator_busy_R', 'UNB2_DC_DC_48V_12V_IOUT_R', 'UNB2_DC_DC_48V_12V_TEMP_R', 'UNB2_DC_DC_48V_12V_VIN_R', 'UNB2_DC_DC_48V_12V_VOUT_R', 'UNB2_EEPROM_Serial_Number_R', 'UNB2_EEPROM_Unique_ID_R', 'UNB2_FPGA_DDR4_SLOT_TEMP_R', 'UNB2_FPGA_POL_CORE_IOUT_R', 'UNB2_FPGA_POL_CORE_TEMP_R', 'UNB2_FPGA_POL_CORE_VOUT_R', 'UNB2_FPGA_POL_ERAM_IOUT_R', 'UNB2_FPGA_POL_ERAM_TEMP_R', 'UNB2_FPGA_POL_ERAM_VOUT_R', 'UNB2_FPGA_POL_HGXB_IOUT_R', 'UNB2_FPGA_POL_HGXB_TEMP_R', 'UNB2_FPGA_POL_HGXB_VOUT_R', 'UNB2_FPGA_POL_PGM_IOUT_R', 'UNB2_FPGA_POL_PGM_TEMP_R', 'UNB2_FPGA_POL_PGM_VOUT_R', 'UNB2_FPGA_POL_RXGXB_IOUT_R', 'UNB2_FPGA_POL_RXGXB_TEMP_R', 'UNB2_FPGA_POL_RXGXB_VOUT_R', 'UNB2_FPGA_POL_TXGXB_IOUT_R', 'UNB2_FPGA_POL_TXGXB_TEMP_R', 'UNB2_FPGA_POL_TXGXB_VOUT_R', 'UNB2_FPGA_QSFP_CAGE_LOS_R', 'UNB2_FPGA_QSFP_CAGE_TEMP_R', 'UNB2_Front_Panel_LED_R', 'UNB2_Front_Panel_LED_RW', 'UNB2_I2C_bus_DDR4_error_R', 'UNB2_I2C_bus_error_R', 'UNB2_I2C_bus_FPGA_PS_error_R', 'UNB2_I2C_bus_PS_error_R', 'UNB2_I2C_bus_QSFP_error_R', 'UNB2_mask_RW', 'UNB2_POL_CLOCK_IOUT_R', 'UNB2_POL_CLOCK_TEMP_R', 'UNB2_POL_CLOCK_VOUT_R', 'UNB2_POL_QSFP_N01_IOUT_R', 'UNB2_POL_QSFP_N01_TEMP_R', 'UNB2_POL_QSFP_N01_VOUT_R', 'UNB2_POL_QSFP_N23_IOUT_R', 'UNB2_POL_QSFP_N23_TEMP_R', 'UNB2_POL_QSFP_N23_VOUT_R', 'UNB2_POL_SWITCH_1V2_IOUT_R', 'UNB2_POL_SWITCH_1V2_TEMP_R', 'UNB2_POL_SWITCH_1V2_VOUT_R', 'UNB2_POL_SWITCH_PHY_IOUT_R', 'UNB2_POL_SWITCH_PHY_TEMP_R', 'UNB2_POL_SWITCH_PHY_VOUT_R', 'UNB2_PWR_off_R', 'UNB2_PWR_off_RW', 'State', 'Status']\n", + "\r\n", + "Attributes:\n", + ">>>\t version_R\n", + ">>>\t opcua_missing_attributes_R\n", + ">>>\t UNB2TR_translator_busy_R\n", + ">>>\t UNB2_DC_DC_48V_12V_IOUT_R\n", + ">>>\t UNB2_DC_DC_48V_12V_TEMP_R\n", + ">>>\t UNB2_DC_DC_48V_12V_VIN_R\n", + ">>>\t UNB2_DC_DC_48V_12V_VOUT_R\n", + ">>>\t UNB2_EEPROM_Serial_Number_R\n", + ">>>\t UNB2_EEPROM_Unique_ID_R\n", + ">>>\t UNB2_FPGA_DDR4_SLOT_TEMP_R\n", + ">>>\t UNB2_FPGA_POL_CORE_IOUT_R\n", + ">>>\t UNB2_FPGA_POL_CORE_TEMP_R\n", + ">>>\t UNB2_FPGA_POL_CORE_VOUT_R\n", + ">>>\t UNB2_FPGA_POL_ERAM_IOUT_R\n", + ">>>\t UNB2_FPGA_POL_ERAM_TEMP_R\n", + ">>>\t UNB2_FPGA_POL_ERAM_VOUT_R\n", + ">>>\t UNB2_FPGA_POL_HGXB_IOUT_R\n", + ">>>\t UNB2_FPGA_POL_HGXB_TEMP_R\n", + ">>>\t UNB2_FPGA_POL_HGXB_VOUT_R\n", + ">>>\t UNB2_FPGA_POL_PGM_IOUT_R\n", + ">>>\t UNB2_FPGA_POL_PGM_TEMP_R\n", + ">>>\t UNB2_FPGA_POL_PGM_VOUT_R\n", + ">>>\t UNB2_FPGA_POL_RXGXB_IOUT_R\n", + ">>>\t UNB2_FPGA_POL_RXGXB_TEMP_R\n", + ">>>\t UNB2_FPGA_POL_RXGXB_VOUT_R\n", + ">>>\t UNB2_FPGA_POL_TXGXB_IOUT_R\n", + ">>>\t UNB2_FPGA_POL_TXGXB_TEMP_R\n", + ">>>\t UNB2_FPGA_POL_TXGXB_VOUT_R\n", + ">>>\t UNB2_FPGA_QSFP_CAGE_LOS_R\n", + ">>>\t UNB2_FPGA_QSFP_CAGE_TEMP_R\n", + ">>>\t UNB2_Front_Panel_LED_R\n", + ">>>\t UNB2_Front_Panel_LED_RW\n", + ">>>\t UNB2_I2C_bus_DDR4_error_R\n", + ">>>\t UNB2_I2C_bus_error_R\n", + ">>>\t UNB2_I2C_bus_FPGA_PS_error_R\n", + ">>>\t UNB2_I2C_bus_PS_error_R\n", + ">>>\t UNB2_I2C_bus_QSFP_error_R\n", + ">>>\t UNB2_mask_RW\n", + ">>>\t UNB2_POL_CLOCK_IOUT_R\n", + ">>>\t UNB2_POL_CLOCK_TEMP_R\n", + ">>>\t UNB2_POL_CLOCK_VOUT_R\n", + ">>>\t UNB2_POL_QSFP_N01_IOUT_R\n", + ">>>\t UNB2_POL_QSFP_N01_TEMP_R\n", + ">>>\t UNB2_POL_QSFP_N01_VOUT_R\n", + ">>>\t UNB2_POL_QSFP_N23_IOUT_R\n", + ">>>\t UNB2_POL_QSFP_N23_TEMP_R\n", + ">>>\t UNB2_POL_QSFP_N23_VOUT_R\n", + ">>>\t UNB2_POL_SWITCH_1V2_IOUT_R\n", + ">>>\t UNB2_POL_SWITCH_1V2_TEMP_R\n", + ">>>\t UNB2_POL_SWITCH_1V2_VOUT_R\n", + ">>>\t UNB2_POL_SWITCH_PHY_IOUT_R\n", + ">>>\t UNB2_POL_SWITCH_PHY_TEMP_R\n", + ">>>\t UNB2_POL_SWITCH_PHY_VOUT_R\n", + ">>>\t UNB2_PWR_off_R\n", + ">>>\t UNB2_PWR_off_RW\n", + ">>>\t State\n", + ">>>\t Status\n", + "Missing Attributes: \n", + " ('2:UNB2TR_translator_busy_R', '2:UNB2_EEPROM_Serial_Number_R', '2:UNB2_I2C_bus_DDR4_error_R', '2:UNB2_I2C_bus_error_R', '2:UNB2_I2C_bus_FPGA_PS_error_R', '2:UNB2_I2C_bus_PS_error_R', '2:UNB2_I2C_bus_QSFP_error_R', '2:UNB2_PWR_off_R', '2:UNB2_PWR_off_RW')\n" ] } ], "source": [ "attr_names = d.get_attribute_list()\n", + "print(attr_names)\n", "\n", + "print(\"\\r\\nAttributes:\")\n", "for i in attr_names:\n", - " exec(\"value = print(i, d.{})\".format(i))\n" + " print(\">>>\\t\",i)\n", + " #exec(\"value = print(i, d.{})\".format(i))\n", + "print(\"Missing Attributes: \\n\", d.opcua_missing_attributes_R)" ] }, { "cell_type": "code", - "execution_count": 39, - "id": "sporting-current", + "execution_count": 20, + "id": "252e49de", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "3.0" + "array([0., 0.])" ] }, - "execution_count": 39, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "d.RCU_mask_RW = [False, False, False, False, False, False, False, False, False, False, False, False,\n", - " False, False, False, False, False, False, False, False, False, False, False, False,\n", - " False, False, False, False, False, False, False, False,]\n", - "time.sleep(1)\n", - "print(d.RCU_mask_RW)\n", - "\n", - "monitor_rate = d.RCU_monitor_rate_RW\n", - "print(\"current monitoring rate: {}, setting to {}\".format(monitor_rate, monitor_rate + 1))\n", - "monitor_rate = monitor_rate + 1\n", - "\n", - "time.sleep(1)\n" + "d.UNB2_POL_QSFP_N23_TEMP_R" ] }, { "cell_type": "code", "execution_count": null, - "id": "sharing-mechanics", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ruled-tracy", + "id": "d348a3c6", "metadata": {}, "outputs": [], "source": [] @@ -143,4 +188,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/sbin/load_ConfigDb.sh b/sbin/load_ConfigDb.sh index 6b5fbb11d31a6f2ce727312111be67486038365e..0fe57087a89dc08ebef51d4679a687ebbf6a144a 100755 --- a/sbin/load_ConfigDb.sh +++ b/sbin/load_ConfigDb.sh @@ -1,32 +1,18 @@ -function help() -{ - echo -e "\nERROR: ${1}\nYou must provide a file that can be accessed from within the Docker container. This is possible for files that\n\t- Have a path in \${HOME} or\n\t- Have a full file path that begins with one of \"/hosthome/\", \"/opt/lofar2.0/tango/\" or \"/opt/lofar2.0/\".\n\nWhy is that? Because the file will be loaded from within the Docker container and only some of the host's file system directories are mounted in the container." - exit -2 -} +#!/bin/bash if [ ${#} -eq 1 ]; then file=${1} else - help "A file name must be provided." + echo "A file name must be provided." + exit 1 fi -# Check if the filename begins with /hosthome/, /opt/lofar2.0/tango or -# /opt/lofar2.0/ or if it is in the ${HOME} directory: -if [ ${1:0:10} != /hosthome/ -a ${1:0:20} != /opt/lofar2.0/tango/ -a ${1:0:14} != /opt/lofar2.0/ ]; then - pushd $(dirname ${file}) >/dev/null - full_path=${PWD} - popd >/dev/null - # Check if the file's directory begins with ${HOME}. Then it can be - # accessed via /hosthome. The replacement will then shorten the result. - home_replaced=${full_path#${HOME}} - if [ ${#home_replaced} -ne ${#full_path} ]; then - if [ ! -f ${file} ]; then - help "The file \"${1}\" does not exist." - fi +# copy file into container to read it from container, as the file's location +# in the container won't be the same as on the host. +docker cp "${file}" "${CONTAINER_NAME_PREFIX}"dsconfig:/tmp/dsconfig-update-settings.json || exit 1 - # The file can be accessed through /hosthome. Modify the parameter. - file=/hosthome${home_replaced}/$(basename ${file}) - fi -fi +# update settings, Do not change -i into -it this will break integration tests in gitlab ci! +docker exec -i "${CONTAINER_NAME_PREFIX}"dsconfig json2tango --write /tmp/dsconfig-update-settings.json -docker exec -it ${TANGO_CONTAINER_ENV} dsconfig json2tango --write ${file} +# somehow json2tango does not return 0 on success +exit 0 diff --git a/sbin/run_integration_test.sh b/sbin/run_integration_test.sh new file mode 100755 index 0000000000000000000000000000000000000000..4e988f7fce03eaf4142193c8156ddbcf60ace0bf --- /dev/null +++ b/sbin/run_integration_test.sh @@ -0,0 +1,49 @@ +#!/bin/bash -e + +if [ -z "$LOFAR20_DIR" ]; then + # We assume we aren't in the PATH, so we can derive our path. + # We need our parent directory. + LOFAR20_DIR_RELATIVE=$(dirname "$0")/.. + + # As an absolute path + LOFAR20_DIR=$(readlink -f "${LOFAR20_DIR_RELATIVE}") +fi + +cd "$LOFAR20_DIR/docker-compose" || exit 1 + +# Make sure builds are recent, and use our building parameters. +make build + +# Start and stop sequence +make stop device-sdp device-recv device-sst device-unb2 device-xst sdptr-sim recv-sim unb2-sim apsct-sim apspu-sim +make start databaseds dsconfig elk + +# Give dsconfig and databaseds time to start +sleep 60 + +# Update the dsconfig +# Do not remove `bash`, otherwise statement ignored by gitlab ci shell! +bash "${LOFAR20_DIR}"/sbin/update_ConfigDb.sh "${LOFAR20_DIR}"/CDB/LOFAR_ConfigDb.json +bash "${LOFAR20_DIR}"/sbin/update_ConfigDb.sh "${LOFAR20_DIR}"/CDB/stations/simulators_ConfigDb.json + +cd "$LOFAR20_DIR/docker-compose" || exit 1 +make start sdptr-sim recv-sim unb2-sim apsct-sim apspu-sim + +# Give the simulators time to start +sleep 5 + +make start device-sdp device-recv device-sst device-unb2 device-xst + +# Give devices time to restart +# TODO(Corne Lukken): Use a nicer more reliable mechanism +sleep 60 + +# Start the integration test +cd "$LOFAR20_DIR/docker-compose" || exit 1 +make start integration-test + +# Give devices time to restart +sleep 60 + +# Run the integration test with the output displayed on stdout +docker start -a "${CONTAINER_NAME_PREFIX}"integration-test diff --git a/sbin/tag_and_push_docker_image.sh b/sbin/tag_and_push_docker_image.sh index ad94ae4b2ca6418e0d89347d4b37b47ef1a16a5a..799ab1cd779bb5caf840685f339080b57916063b 100755 --- a/sbin/tag_and_push_docker_image.sh +++ b/sbin/tag_and_push_docker_image.sh @@ -1,4 +1,4 @@ -#! /usr/bin/env bash -e +#!/bin/bash -e # Tag and push which image version? DOCKER_TAG=latest @@ -10,16 +10,16 @@ SKA_REPO="nexus.engageska-portugal.pt/ska-docker" LOFAR_REPO="git.astron.nl:5000/lofar2.0/tango" # Compile a list of the SKA images -SKA_IMAGES=$(for i in $(docker images | egrep ${DOCKER_TAG} | egrep ${SKA_REPO} | cut -d' ' -f1); do printf "%s " ${i}; done) +SKA_IMAGES=$(for i in $(docker images | grep -E ${DOCKER_TAG} | grep -E ${SKA_REPO} | cut -d' ' -f1); do printf "%s " "${i}"; done) # Compile a list of LOFAR2.0 images -LOFAR_IMAGES=$(for i in $(docker images | egrep ${DOCKER_TAG} | egrep -v "${SKA_REPO}|${LOFAR_REPO}" | cut -d' ' -f1); do printf "%s " ${i}; done) +LOFAR_IMAGES=$(for i in $(docker images | grep -E ${DOCKER_TAG} | grep -E -v "${SKA_REPO}|${LOFAR_REPO}" | cut -d' ' -f1); do printf "%s " "${i}"; done) function tag_and_push() { ( - docker tag ${1} ${2} - docker push ${2} + docker tag "${1}" "${2}" + docker push "${2}" ) & } @@ -27,14 +27,14 @@ function tag_and_push() # and push them to the LOFAR2.0 repo for IMAGE in ${SKA_IMAGES}; do PUSH_IMAGE=${IMAGE//${SKA_REPO}/${LOFAR_REPO}}:${VERSION} - tag_and_push ${IMAGE} ${PUSH_IMAGE} + tag_and_push "${IMAGE}" "${PUSH_IMAGE}" done # Rename the LOFAR2.0 images for the LOFAR2.0 repo # and push them to the LOFAR2.0 repo for IMAGE in ${LOFAR_IMAGES}; do - PUSH_IMAGES=${LOFAR_REPO}/${IMAGE}:${VERSIN} - tag_and_push ${IMAGE} ${PUSH_IMAGE} + PUSH_IMAGE=${LOFAR_REPO}/${IMAGE}:${VERSION} + tag_and_push "${IMAGE}" "${PUSH_IMAGE}" done wait diff --git a/sbin/update_ConfigDb.sh b/sbin/update_ConfigDb.sh index fc3dc051b95b8eee5bb679088e0877da37aae8fa..1255f1ea141a75940f2cd858dfc2b40818bd6ec2 100755 --- a/sbin/update_ConfigDb.sh +++ b/sbin/update_ConfigDb.sh @@ -1,37 +1,18 @@ -function help() -{ - echo -e "\nERROR: ${1}\nYou must provide a file that can be accessed from within the Docker container. This is possible for files that\n\t- Have a path in \${HOME} or\n\t- Have a full file path that begins with one of \"/hosthome/\", \"/opt/lofar2.0/tango/\" or \"/opt/lofar2.0/\".\n\nWhy is that? Because the file will be loaded from within the Docker container and only some of the host's file system directories are mounted in the container." - exit -2 -} +#!/bin/bash if [ ${#} -eq 1 ]; then file=${1} else - help "A file name must be provided." + echo "A file name must be provided." + exit 1 fi -# Check if the filename begins with /hosthome/, /opt/lofar2.0/tango or -# /opt/lofar2.0/ or if it is in the ${HOME} directory: -if [ ${1:0:10} != /hosthome/ -a ${1:0:20} != /opt/lofar2.0/tango/ -a ${1:0:14} != /opt/lofar2.0/ ]; then - pushd $(dirname ${file}) >/dev/null - full_path=${PWD} - popd >/dev/null - # Check if the file's directory begins with ${HOME}. Then it can be - # accessed via /hosthome. The replacement will then shorten the result. - home_replaced=${full_path#${HOME}} - if [ ${#home_replaced} -ne ${#full_path} ]; then - if [ ! -f ${file} ]; then - help "The file \"${1}\" does not exist." - fi +# copy file into container to read it from container, as the file's location +# in the container won't be the same as on the host. +docker cp "${file}" "${CONTAINER_NAME_PREFIX}"dsconfig:/tmp/dsconfig-update-settings.json || exit 1 - # The file can be accessed through /hosthome. Modify the parameter. - file=/hosthome${home_replaced}/$(basename ${file}) - else - # The file is in one of the two: /opt/lofar2.0/tango/ /opt/lofar2.0/ - # Provide the full path since it is accessible from within the docker - # image because both directories are mounted. - file=${full_path}/$(basename ${file}) - fi -fi +# update settings, Do not change -i into -it this will break integration tests in gitlab ci! +docker exec -i "${CONTAINER_NAME_PREFIX}"dsconfig json2tango --write --update /tmp/dsconfig-update-settings.json -docker exec -it ${TANGO_CONTAINER_ENV} dsconfig json2tango --write --update ${file} +# somehow json2tango does not return 0 on success +exit 0 diff --git a/tangostationcontrol/.stestr.conf b/tangostationcontrol/.stestr.conf new file mode 100644 index 0000000000000000000000000000000000000000..59b161cddad8a253059cf201b8872bc946f78c64 --- /dev/null +++ b/tangostationcontrol/.stestr.conf @@ -0,0 +1,3 @@ +[DEFAULT] +test_path=${TESTS_DIR:-./tangostationcontrol/test} +top_dir=./ diff --git a/devices/README.md b/tangostationcontrol/README.md similarity index 91% rename from devices/README.md rename to tangostationcontrol/README.md index 0604c3a4d1d90b7d99ab1f35cee1922c6ee99371..0fef1b58aa82065b03353315cdd63ded8e2a3cd2 100644 --- a/devices/README.md +++ b/tangostationcontrol/README.md @@ -3,7 +3,7 @@ This code provides an attribute_wrapper class in place of attributes for tango devices. the attribute wrappers contain additional code that moves a lot of the complexity and redundant code to the background. -The tango Device class is also abstracted further to a "hardware_device" class. This class wraps +The tango Device class is also abstracted further to a "lofar_device" class. This class wraps The only things required on the users part are to declare the attributes using the attribute_wrapper (see `example/example_device`), declare what client the attribute has to use in the initialisation and provide support for the used clients. diff --git a/tangostationcontrol/requirements.txt b/tangostationcontrol/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..b1620255b5a45c9e3f653661e65de6732fd93a07 --- /dev/null +++ b/tangostationcontrol/requirements.txt @@ -0,0 +1,13 @@ +# the order of packages is of significance, because pip processes them in the +# order of appearance. Changing the order has an impact on the overall +# integration process, which may cause wedges in the gate later. + +asyncua >= 0.9.90 # LGPLv3 +PyMySQL[rsa] >= 1.0.2 # MIT +sqlalchemy >= 1.4.26 #MIT +GitPython >= 3.1.24 # BSD +snmp >= 0.1.7 # GPL3 +h5py >= 3.5.0 # BSD +psutil >= 5.8.0 # BSD +docker >= 5.0.3 # Apache 2 +python-logstash-async >= 2.3.0 # MIT \ No newline at end of file diff --git a/tangostationcontrol/setup.cfg b/tangostationcontrol/setup.cfg new file mode 100644 index 0000000000000000000000000000000000000000..e6101c890893c961e1eff27ce3d3ab4e019ea312 --- /dev/null +++ b/tangostationcontrol/setup.cfg @@ -0,0 +1,58 @@ +[metadata] +name = tangostationcontrol +version = attr: tangostationcontrol.__version__ +summary = LOFAR 2.0 Station Control +description_file = + README.md +description_content_type = text/x-rst; charset=UTF-8 +author = ASTRON +home_page = https://astron.nl +project_urls = + Bug Tracker = https://support.astron.nl/jira/projects/L2SS/issues/ + Source Code = https://git.astron.nl/lofar2.0/tango +license = Apache-2 +classifier = + Environment :: Console + License :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + +[options] +package_dir= + =./ +packages=find: +python_requires = >=3.6 + +[options.packages.find] +where=./ + +[options.entry_points] +console_scripts = + l2ss-apsct = tangostationcontrol.devices.apsct:main + l2ss-apspu = tangostationcontrol.devices.apspu:main + l2ss-boot = tangostationcontrol.devices.boot:main + l2ss-docker-device = tangostationcontrol.devices.docker_device:main + l2ss-observation = tangostationcontrol.devices.observation:main + l2ss-observation-control = tangostationcontrol.devices.observation_control:main + l2ss-receiver = tangostationcontrol.devices.recv:main + l2ss-sdp = tangostationcontrol.devices.sdp.sdp:main + l2ss-sst = tangostationcontrol.devices.sdp.sst:main + l2ss-statistics-reader = tangostationcontrol.statistics_writer.statistics_reader:main + l2ss-statistics-writer = tangostationcontrol.statistics_writer.statistics_writer:main + l2ss-unb2 = tangostationcontrol.devices.unb2:main + l2ss-xst = tangostationcontrol.devices.sdp.xst:main + +# The following entry points should eventually be removed / replaced + l2ss-cold-start = tangostationcontrol.toolkit.lts_cold_start:main + l2ss-hardware-device-template = tangostationcontrol.examples.HW_device_template:main + l2ss-ini-device = tangostationcontrol.examples.load_from_disk.ini_device:main + l2ss-parse-statistics-packet = tangostationcontrol.devices.sdp.statistics_packet:main + l2ss-random-data = tangostationcontrol.test.devices.random_data:main + l2ss-snmp = tangostationcontrol.examples.snmp.snmp:main + l2ss-version = tangostationcontrol.common.lofar_version:main diff --git a/tangostationcontrol/setup.py b/tangostationcontrol/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..6356812fdc2951fff6af9659feca273df06efca3 --- /dev/null +++ b/tangostationcontrol/setup.py @@ -0,0 +1,7 @@ +import setuptools + +with open('requirements.txt') as f: + required = f.read().splitlines() + +# Requires: setup.cfg +setuptools.setup(install_requires=required) diff --git a/tangostationcontrol/tangostationcontrol/__init__.py b/tangostationcontrol/tangostationcontrol/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c6e48f3e8c0b11146b60c5fb7b8b2285fd7124d0 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/__init__.py @@ -0,0 +1,3 @@ +from tangostationcontrol.common.lofar_version import get_version + +__version__ = get_version() diff --git a/devices/clients/README.md b/tangostationcontrol/tangostationcontrol/clients/README.md similarity index 92% rename from devices/clients/README.md rename to tangostationcontrol/tangostationcontrol/clients/README.md index 3613344461e8abb64e5a68a1d30c68b3927d22b4..083420b38dc611fd8096110ca42d46c375d3db60 100644 --- a/devices/clients/README.md +++ b/tangostationcontrol/tangostationcontrol/clients/README.md @@ -1,4 +1,4 @@ this folder contains all the comms_client implementations for organisation ### How to add a new client -soon™ \ No newline at end of file +soon™ diff --git a/devices/devices/__init__.py b/tangostationcontrol/tangostationcontrol/clients/__init__.py similarity index 100% rename from devices/devices/__init__.py rename to tangostationcontrol/tangostationcontrol/clients/__init__.py diff --git a/devices/clients/attribute_wrapper.py b/tangostationcontrol/tangostationcontrol/clients/attribute_wrapper.py similarity index 80% rename from devices/clients/attribute_wrapper.py rename to tangostationcontrol/tangostationcontrol/clients/attribute_wrapper.py index 99312919c0631f85c64cd3aec097a00b316f12f4..718ea431d6d24962ad7f437e3941e7609f89927f 100644 --- a/devices/clients/attribute_wrapper.py +++ b/tangostationcontrol/tangostationcontrol/clients/attribute_wrapper.py @@ -1,9 +1,8 @@ from tango.server import attribute from tango import AttrWriteType - import numpy -from devices.device_decorators import only_when_on, fault_on_error +from tangostationcontrol.devices.device_decorators import only_when_on, fault_on_error import logging logger = logging.getLogger() @@ -27,22 +26,27 @@ class attribute_wrapper(attribute): """ # ensure the type is a numpy array. - # see also https://pytango.readthedocs.io/en/stable/server_api/server.html?highlight=devlong#module-tango.server for + # see also https://pytango.readthedocs.io/en/stable/server_api/server.html?highlight=devlong#module-tango.server for # more details about type conversion Python/numpy -> PyTango if "numpy" not in str(datatype) and datatype != str: - raise TypeError("Attribute needs to be a Tango-supported numpy or str type, but has type \"%s\"" % (datatype,)) + raise ValueError("Attribute needs to be a Tango-supported numpy or str type, but has type \"%s\"" % (datatype,)) + + """ + Numpy has a depracated string type called numpy.str_. + this behaves differently from numpy.str (which is literally just an str. + """ + if datatype == numpy.str_: + raise Exception("numpy.str_ type not supported, please use numpy.str instead") self.comms_id = comms_id # store data that can be used to identify the comms interface to use. not used by the wrapper itself self.comms_annotation = comms_annotation # store data that can be used by the comms interface. not used by the wrapper itself - self.numpy_type = datatype # tango changes our attribute to their representation (E.g numpy.int64 becomes "DevLong64") self.init_value = init_value is_scalar = dims == (1,) - # tango doesn't recognise numpy.str_, for consistencies sake we convert it here and hide this from the top level - # NOTE: discuss, idk if this is an important detail somewhere else - if datatype is numpy.str_: - datatype = str + + + self.numpy_type = datatype # tango changes our attribute to their representation (E.g numpy.int64 becomes "DevLong64") # check if not scalar if is_scalar: @@ -131,7 +135,15 @@ class attribute_wrapper(attribute): else: numpy_dims = dims - value = numpy.zeros(numpy_dims, dtype=self.numpy_type) + if self.dim_x == 1: + + if self.numpy_type == str: + value = '' + else: + value = self.numpy_type(0) + else: + value = numpy.zeros(numpy_dims, dtype=self.numpy_type) + return value def set_comm_client(self, client): @@ -142,9 +154,17 @@ class attribute_wrapper(attribute): try: self.read_function, self.write_function = client.setup_attribute(self.comms_annotation, self) except Exception as e: + raise Exception(f"Exception while setting {client.__class__.__name__} attribute with annotation: '{self.comms_annotation}'") from e + + async def async_set_comm_client(self, client): + """ + Asynchronous version of set_comm_client. + """ + try: + self.read_function, self.write_function = await client.setup_attribute(self.comms_annotation, self) + except Exception as e: + raise Exception(f"Exception while setting {client.__class__.__name__} attribute with annotation: '{self.comms_annotation}'") from e - logger.error("Exception while setting {} attribute with annotation: '{}' {}".format(client.__class__.__name__, self.comms_annotation, e)) - raise Exception("Exception while setting %s attribute with annotation: '%s'", client.__class__.__name__, self.comms_annotation) from e def set_pass_func(self): def pass_func(value=None): diff --git a/tangostationcontrol/tangostationcontrol/clients/comms_client.py b/tangostationcontrol/tangostationcontrol/clients/comms_client.py new file mode 100644 index 0000000000000000000000000000000000000000..2c28d2c70214935d32ffdcb21386ba841e7e5668 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/clients/comms_client.py @@ -0,0 +1,268 @@ +from threading import Thread +import time +import asyncio +from abc import ABC, abstractmethod + +import logging +logger = logging.getLogger() + +class AbstractCommClient(ABC): + @abstractmethod + def start(self): + """ Start communication with the client. """ + + @abstractmethod + def stop(self): + """ Stop communication with the client. """ + + def ping(self): + """ Check whether the connection is still alive. + + Clients that override this method must raise an Exception if the + connection died. """ + pass + + @abstractmethod + def setup_attribute(self, annotation, attribute): + """ + This function returns a (read_function, write_function) tuple for the provided attribute with the provided annotation. + + The setup-attribute has access to the comms_annotation provided to the attribute wrapper to pass along to the comms client + as well as a reference to the attribute itself. + + The read_function must return a single value, representing the current value of the attribute. + + The write_function must take a single value, write it, and return None. + + Examples: + - File system: get_mapping returns functions that read/write a fixed + number of bytes at a fixed location in a file. (SEEK) + - OPC-UA: traverse the OPC-UA tree until the node is found. + Then return the read/write functions for that node which automatically + convert values between Python and OPC-UA. + """ + +class CommClient(AbstractCommClient, Thread): + """ + Abstracts communication with a client, for instance, over the network, by handling connect(), disconnect(), and ping() + primitives. + """ + + def __init__(self, fault_func, try_interval=2): + """ + + """ + self.fault_func = fault_func + self.try_interval = try_interval + self.stopping = False + self.connected = False + + super().__init__(daemon=True) + + def connect(self): + """ + Function used to connect to the client. + + Throws an Exception if the connection cannot be established. + """ + self.connected = True + + def disconnect(self): + """ + Function used to connect to the client. + """ + self.connected = False + + def run(self): + self.stopping = False + while not self.stopping: + if not self.connected: + # we (re)try only once, to catch exotic network issues. if the infra or hardware is down, + # our device cannot help, and must be reinitialised after the infra or hardware is fixed. + try: + self.connect() + except Exception as e: + logger.exception("Fault condition in communication detected.") + self.fault_func() + return + + # keep checking if the connection is still alive + try: + while not self.stopping: + self.ping() + time.sleep(self.try_interval) + except Exception as e: + logger.exception("Fault condition in communication detected.") + + # technically, we may not have dropped the connection, but encounter a different error. so explicitly disconnect. + self.disconnect() + + # signal that we're disconnected + self.fault_func() + + # don't enter a spam-connect loop if faults immediately occur + time.sleep(self.try_interval) + + def ping(self): + """ Check whether the connection is still alive. + + Clients that override this method must raise an Exception if the + connection died. """ + pass + + def stop(self): + """ + Stop connecting & disconnect. Can take a few seconds for the timeouts to hit. + """ + + if not self.ident: + # have not yet been started, so nothing to do + return + + self.stopping = True + self.join() + + self.disconnect() + +class AsyncCommClient(object): + """ + Abstracts communication with a client, for instance, over the network, by handling connect(), disconnect(), and ping() + primitives. + + asyncio version of the CommClient. Also does not reconnect if the connection is lost. + """ + + def __init__(self, fault_func=lambda: None, event_loop=None): + """ + Create an Asynchronous communication client. + + fault_func: Function to call to put the device to FAULT if an error is detected. + event_loop: Aysncio event loop to use. If None, a new event loop is created and + run in a separate thread. Only share event loops if any of the functions + executed doesn't stall, as asyncio used a cooperative multitasking model. + + If the executed functions can stall (for a bit), use a dedicated loop to avoid + interfering with other users of the event loop. + + All coroutines need to be executed in this loop, which wil also be stored + as the `event_loop` member of this object. + """ + self.fault_func = fault_func + self.running = False + + if event_loop is None: + # Run a dedicated event loop for communications + # + # All co-routines need to be called through this event loop, + # for example using asyncio.run_coroutine_threadsafe(coroutine, event_loop). + + def run_loop(loop: asyncio.AbstractEventLoop) -> None: + asyncio.set_event_loop(loop) + loop.run_forever() + + self.event_loop = asyncio.new_event_loop() + self.event_loop_thread = Thread(target=run_loop, args=(self.event_loop,), name=f"AsyncCommClient {self.name()} event loop", daemon=True) + self.event_loop_thread.start() + else: + self.event_loop = event_loop + self.event_loop_thread = None + + def __del__(self): + if self.event_loop_thread is not None: + # signal our event loop thread to stop + self.event_loop.call_soon_threadsafe(self.event_loop.stop) + + # reap our event loop thread once it is done processing tasks + self.event_loop_thread.join() + + def name(self): + """ The name of this CommClient, for use in logs. """ + return self.__class__.__name__ + + @abstractmethod + async def connect(self): + """ + Function used to connect to the client, and any + post init. + """ + + @abstractmethod + async def disconnect(self): + """ + Function used to disconnect from the client. + """ + + async def watch_connection(self): + """ Notice when the connection goes down. """ + + try: + logger.info(f"[AsyncCommClient {self.name()}] Start watching") + + while self.running: + # ping will throw in case of connection issues + try: + await self.ping() + except Exception as e: + logger.exception(f"[AsyncCommClient {self.name()}] Ping failed: connection considered lost") + + # connection error, go to fault + self.fault_func() + + # disconnect will cancel us + await self.disconnect() + + # always have a backup plan + return + + # don't spin, sleep for a while + await asyncio.sleep(2) + except asyncio.CancelledError as e: + pass + except Exception as e: + # log immediately, or the exception will only be printed once this task is awaited + logger.exception(f"[AsyncCommClient {self.name()}] Exception raised while watching") + + raise + finally: + logger.info(f"[AsyncCommClient {self.name()}] Stop watching") + + async def ping(self): + return + + async def start(self): + if self.running: + # already running + return + + await self.connect() + self.running = True + + # watch connection + self.watch_connection_task = asyncio.create_task(self.watch_connection()) + + async def stop(self): + if not self.running: + # already stopped + return + + self.running = False + + # cancel & reap watcher + self.watch_connection_task.cancel() + try: + await self.watch_connection_task + except asyncio.CancelledError as e: + pass + except Exception as e: + logger.exception(f"[AsyncCommClient {self.name()}] Watcher thread raised exception") + + # the task stopped eithr way, so no need to bother our caller with this + + await self.disconnect() + + def sync_stop(self): + """ Synchronous version of stop(). """ + + future = asyncio.run_coroutine_threadsafe(self.stop(), self.event_loop) + return future.result() + diff --git a/tangostationcontrol/tangostationcontrol/clients/docker_client.py b/tangostationcontrol/tangostationcontrol/clients/docker_client.py new file mode 100644 index 0000000000000000000000000000000000000000..a7b487b66656f727b9bd794fcadf9fbe9c50e7fb --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/clients/docker_client.py @@ -0,0 +1,52 @@ +import logging +import docker + +from .comms_client import AsyncCommClient + +logger = logging.getLogger() + +class DockerClient(AsyncCommClient): + """ + Controls & queries running docker containers. + """ + + def __init__(self, base_url, fault_func, event_loop=None): + super().__init__(fault_func, event_loop) + + self.base_url = base_url + + async def connect(self): + self.client = docker.DockerClient(self.base_url) + + async def ping(self): + # Raises if the server is unresponsive + self.client.ping() + + async def disconnect(self): + self.client = None + + async def setup_attribute(self, annotation, attribute): + """ + MANDATORY function: is used by the attribute wrapper to get read/write functions. must return the read and write functions + """ + + container_name = annotation["container"] + + def read_function(): + try: + container = self.client.containers.get(container_name) + except docker.errors.NotFound: + return False + + # expected values: running, restarting, paused, exited, created + return container.status == 'running' + + def write_function(value): + container = self.client.containers.get(container_name) + + if value: + container.start() + else: + container.stop() + + return read_function, write_function diff --git a/tangostationcontrol/tangostationcontrol/clients/opcua_client.py b/tangostationcontrol/tangostationcontrol/clients/opcua_client.py new file mode 100644 index 0000000000000000000000000000000000000000..ce4447de3b9c8339bd36cc1e75a04ec6867b04de --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/clients/opcua_client.py @@ -0,0 +1,274 @@ +import socket +import numpy +import asyncua +import asyncio +from asyncua import Client + +from tangostationcontrol.clients.comms_client import AsyncCommClient + +import logging +logger = logging.getLogger() + +__all__ = ["OPCUAConnection", "event_loop"] + +numpy_to_OPCua_dict = { + numpy.bool_: asyncua.ua.VariantType.Boolean, + numpy.int8: asyncua.ua.VariantType.SByte, + numpy.uint8: asyncua.ua.VariantType.Byte, + numpy.int16: asyncua.ua.VariantType.Int16, + numpy.uint16: asyncua.ua.VariantType.UInt16, + numpy.int32: asyncua.ua.VariantType.Int32, + numpy.uint32: asyncua.ua.VariantType.UInt32, + numpy.int64: asyncua.ua.VariantType.Int64, + numpy.uint64: asyncua.ua.VariantType.UInt64, + numpy.float32: asyncua.ua.VariantType.Float, + numpy.double: asyncua.ua.VariantType.Double, + numpy.float64: asyncua.ua.VariantType.Double, + numpy.str: asyncua.ua.VariantType.String +} + +def numpy_to_opcua(numpy_val): + """ Convert a numpy type to a corresponding opcua Variant type. """ + + numpy_type = type(numpy_val) + + assert numpy_type not in [list, numpy.array], "Converting arrays not yet supported." + + try: + ua_type = numpy_to_OPCua_dict[numpy_type] + except KeyError as e: + raise TypeError(f"Could not convert {numpy_val} (type {type(numpy_val).__name__}) to an OPC UA type.") from e + + return asyncua.ua.uatypes.Variant(Value=numpy_val, VariantType=ua_type) + +class OPCUAConnection(AsyncCommClient): + """ + Connects to OPC-UA in the foreground or background, and sends HELLO + messages to keep a check on the connection. On connection failure, reconnects once. + """ + + def __init__(self, address, namespace, timeout, fault_func, event_loop=None): + """ + Create the OPC ua client and connect() to it and get the object node + """ + + self.client = Client(address, int(timeout)) + self.namespace = namespace + + # prefix path to all nodes with this. this allows the user to switch trees more easily. + self.node_path_prefix = [] + + super().__init__(fault_func, event_loop) + + def _servername(self): + return self.client.server_url.geturl() + + async def connect(self): + """ + Try to connect to the client + """ + + logger.debug(f"Connecting to server {self._servername()}") + + try: + await self.client.connect() + except (socket.error, IOError, OSError) as e: + raise IOError(f"Could not connect to OPC-UA server {self._servername()}") from e + + logger.debug(f"Connected to OPC-UA server {self._servername()}") + + # determine namespace used + if type(self.namespace) is str: + self.name_space_index = await self.client.get_namespace_index(self.namespace) + elif type(self.namespace) is int: + self.name_space_index = self.namespace + else: + raise TypeError(f"namespace must be of type str or int, but is of type {type(self.namespace).__name__}") + + self.obj = self.client.get_objects_node() + + async def disconnect(self): + """ + disconnect from the client + """ + + await self.client.disconnect() + + async def ping(self): + """ + ping the client to make sure the connection with the client is still functional. + """ + try: + # do a cheap call. NOTE: send_hello is not allowed after establishing a connection, + # so cannot be used here. see https://reference.opcfoundation.org/v104/Core/docs/Part6/7.1.3/ + _ = await self.client.get_namespace_array() + except Exception as e: + raise IOError("Lost connection to server %s: %s", self._servername(), e) + + def get_node_path(self, annotation): + """ + Return the path of a node as it will be looked up on the server. + """ + + if isinstance(annotation, dict): + # check if required path inarg is present + if annotation.get('path') is None: + raise Exception("OPC-ua mapping requires a path argument in the annotation, was given: %s", annotation) + + path = annotation.get("path") # required + elif isinstance(annotation, list): + path = annotation + else: + raise Exception("OPC-ua mapping requires either a list of the path or dict with the path. Was given %s type containing: %s", type(annotation), annotation) + + + # add path prefix + path = self.node_path_prefix + path + + # prepend namespace index for each element if none is given + path = [name if ':' in name else f'{self.name_space_index}:{name}' for name in path] + + return path + + + async def setup_protocol_attribute(self, annotation, attribute): + # process the annotation + path = self.get_node_path(annotation) + + try: + node = await self.obj.get_child(path) + except Exception as e: + logger.exception("Could not get node: %s on server %s", path, self._servername()) + raise Exception("Could not get node: %s on server %s", path, self._servername()) from e + + # get all the necessary data to set up the read/write functions from the attribute_wrapper + dim_x = attribute.dim_x + dim_y = attribute.dim_y + ua_type = numpy_to_OPCua_dict[attribute.numpy_type] # convert the numpy type to a corresponding UA type + + # configure and return the read/write functions + prot_attr = ProtocolAttribute(node, dim_x, dim_y, ua_type) + + try: + # NOTE: debug statement tries to get the qualified name, this may not always work. in that case forgo the name and just print the path + node_name = str(node.get_browse_name())[len("QualifiedName(2:"):] + logger.debug("connected OPC ua node {} of type {} to attribute with dimensions: {} x {} ".format(str(node_name)[:len(node_name)-1], str(ua_type)[len("VariantType."):], dim_x, dim_y)) + except: + pass + + return prot_attr + + async def setup_attribute(self, annotation, attribute): + prot_attr = await self.setup_protocol_attribute(annotation, attribute) + + # Tango will call these from a separate polling thread. + def read_function(): + return asyncio.run_coroutine_threadsafe(prot_attr.read_function(), self.event_loop).result() + + def write_function(value): + asyncio.run_coroutine_threadsafe(prot_attr.write_function(value), self.event_loop).result() + + # return the read/write functions + return read_function, write_function + + + async def _call_method(self, method_path, *args): + method_path = self.get_node_path(method_path) + + # convert the arguments to OPC UA types + args = [numpy_to_opcua(arg) for arg in args] + + try: + # call method in its parent node + node = await self.obj.get_child(method_path[:-1]) if len(method_path) > 1 else self.obj + result = await node.call_method(method_path[-1], *args) + except Exception as e: + raise Exception(f"Calling method {method_path} failed") from e + + return result + + + def call_method(self, method_path, *args): + return asyncio.run_coroutine_threadsafe(self._call_method(method_path, *args), self.event_loop).result() + + +class ProtocolAttribute: + """ + This class provides a small wrapper for the OPC ua read/write functions in order to better organise the code + """ + + def __init__(self, node, dim_x, dim_y, ua_type): + self.node = node + self.dim_y = dim_y + self.dim_x = dim_x + self.ua_type = ua_type + + async def read_function(self): + """ + Read_R function + """ + + value = await self.node.get_value() + + try: + if self.dim_y + self.dim_x == 1: + # scalar + return value + elif self.dim_y != 0: + # 2D array + value = numpy.array(numpy.split(numpy.array(value), indices_or_sections=self.dim_y)) + else: + # 1D array + value = numpy.array(value) + + return value + except Exception as e: + # Log "value" that gave us this issue + raise ValueError(f"Failed to parse atribute value retrieved from OPC-UA: {value}") from e + + + async def write_function(self, value): + """ + write_RW function + """ + + if self.dim_y != 0: + # flatten array, convert to python array + value = numpy.concatenate(value).tolist() + elif self.dim_x != 1: + # make sure it is a python array + value = value.tolist() if type(value) == numpy.ndarray else value + + try: + await self.node.set_data_value(asyncua.ua.uatypes.Variant(Value=value, VariantType=self.ua_type)) + except (TypeError, asyncua.ua.uaerrors.BadTypeMismatch) as e: + # A type conversion went wrong or there is a type mismatch. + # + # This is either the conversion us -> opcua in our client, or client -> server. + # Report all types involved to allow assessment of the location of the error. + if type(value) == list: + our_type = "list({dtype}) x ({dimensions})".format( + dtype=(type(value[0]).__name__ if value else ""), + dimensions=len(value)) + else: + our_type = "{dtype}".format( + dtype=type(value)) + + is_scalar = (self.dim_x + self.dim_y) == 1 + + if is_scalar: + expected_server_type = "{dtype} (scalar)".format( + dtype=self.ua_type) + else: + expected_server_type = "{dtype} x ({dim_x}, {dim_y})".format( + dtype=self.ua_type, + dim_x=self.dim_x, + dim_y=self.dim_y) + + actual_server_type = "{dtype} x {dimensions}".format( + dtype=await self.node.read_data_type_as_variant_type(), + dimensions=(await self.node.read_array_dimensions()) or "(dimensions unknown)") + + attribute_name = (await self.node.read_display_name()).to_string() + + raise TypeError(f"Cannot write value to OPC-UA attribute '{attribute_name}': tried to convert data type {our_type} to expected server type {expected_server_type}, server reports type {actual_server_type}") from e diff --git a/tangostationcontrol/tangostationcontrol/clients/statistics_client.py b/tangostationcontrol/tangostationcontrol/clients/statistics_client.py new file mode 100644 index 0000000000000000000000000000000000000000..2790197bdb90f2483d872f0dfd5978fa088d4980 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/clients/statistics_client.py @@ -0,0 +1,135 @@ +from queue import Queue +import logging +import numpy + +from .comms_client import AsyncCommClient +from .tcp_replicator import TCPReplicator +from .udp_receiver import UDPReceiver + +from tangostationcontrol.devices.sdp.statistics_collector import StatisticsConsumer + +logger = logging.getLogger() + + +class StatisticsClient(AsyncCommClient): + """ + Collects statistics packets over UDP, forwards them to a StatisticsCollector, + and provides a CommClient interface to expose points to a Device Server. + """ + + def __init__(self, collector, udp_options, tcp_options, fault_func, event_loop=None, queuesize=1024): + """ + Create the statistics client and connect() to it and get the object node. + + collector: a subclass of StatisticsCollector that specialises in processing the received packets. + host: hostname to listen on + port: port number to listen on + """ + + self.udp_options = udp_options + self.tcp_options = tcp_options + self.queuesize = queuesize + self.collector = collector + + super().__init__(fault_func, event_loop) + + @staticmethod + def _queue_fill_percentage(queue: Queue): + try: + return 100 * queue.qsize() / queue.maxsize if queue.maxsize else 0 + except NotImplementedError: + # some platforms don't have qsize(), nothing we can do here + return 0 + + async def connect(self): + """ + Function used to connect to the client. + """ + self.collector_queue = Queue(maxsize=self.queuesize) + + self.tcp = TCPReplicator(self.tcp_options, self.queuesize) + self.statistics = StatisticsConsumer(self.collector_queue, self.collector) + + self.udp = UDPReceiver([self.collector_queue, self.tcp], + self.udp_options) + + async def ping(self): + if not self.statistics.is_alive(): + raise Exception("Statistics processing thread died unexpectedly") + + if not self.udp.is_alive(): + raise Exception("UDP thread died unexpectedly") + + if not self.tcp.is_alive(): + raise Exception("TCPReplicator thread died unexpectedly") + + async def disconnect(self): + # explicit disconnect, instead of waiting for the GC to kick in after "del" below + try: + self.statistics.disconnect() + except Exception: + logger.exception("Could not disconnect statistics processing class") + + try: + self.udp.disconnect() + except Exception: + # nothing we can do, but we should continue cleaning up + logger.exception("Could not disconnect UDP receiver class") + + try: + self.tcp.disconnect() + except Exception: + logger.exception("Could not disconnect TCPReplicator class") + #logger.log_exception("Could not disconnect TCPReplicator class") + + del self.tcp + del self.udp + del self.statistics + del self.collector_queue + + async def setup_attribute(self, annotation, attribute): + """ + MANDATORY function: is used by the attribute wrapper to get read/write functions. must return the read and write functions + """ + + parameter = annotation["parameter"] + + # redirect to right object. this works as long as the parameter names are unique among them. + if annotation["type"] == "statistics": + def read_function(): + return self.collector.parameters[parameter] + elif annotation["type"] == "udp": + def read_function(): + return self.udp.parameters[parameter] + elif annotation["type"] == "queue": + if parameter == "collector_fill_percentage": + def read_function(): + return numpy.uint64(self._queue_fill_percentage(self.collector_queue)) + elif parameter == "replicator_fill_percentage": + def read_function(): + return numpy.uint64(self._queue_fill_percentage(self.tcp.queue)) + else: + raise ValueError("Unknown queue parameter requested: %s" % parameter) + elif annotation["type"] == "replicator": + if parameter == "clients": + def read_function(): + return numpy.array(self.tcp.clients(),dtype=numpy.str) + elif parameter == "nof_bytes_sent": + def read_function(): + return numpy.uint64(self.tcp.nof_bytes_sent) + elif parameter == "nof_packets_sent": + def read_function(): + return numpy.uint64(self.tcp.nof_packets_sent) + elif parameter == "nof_tasks_pending": + def read_function(): + return numpy.uint64(self.tcp.nof_tasks_pending) + else: + raise ValueError("Unknown replicator parameter requested: %s" % parameter) + + def write_function(value): + """ + Not used here + """ + pass + + return read_function, write_function diff --git a/tangostationcontrol/tangostationcontrol/clients/statistics_client_thread.py b/tangostationcontrol/tangostationcontrol/clients/statistics_client_thread.py new file mode 100644 index 0000000000000000000000000000000000000000..3da8f76ac135fd4fb631f1de98518ff74f9ec2f9 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/clients/statistics_client_thread.py @@ -0,0 +1,45 @@ +from abc import ABC +from abc import abstractmethod +import logging + +logger = logging.getLogger() + + +class StatisticsClientThread(ABC): + + # Maximum time to wait for the Thread to get unstuck, if we want to stop + DISCONNECT_TIMEOUT = 10 + + @property + @abstractmethod + def _options(self) -> dict: + """Implement me to return reasonable defaults + + Don't create the variable inside this property, instead create a class + variable inside the child class and return that.""" + pass + + def _parse_options(self, options: dict) -> dict: + """Parse the arguments""" + + # Parse options if any otherwise return defaults + if not options: + return self._options + + # Shallow copy the options, native data types and strings are immutable + temp_options = self._options.copy() + + # Find all matching keys in the options arguments and override + for option, value in options.items(): + if option in temp_options: + temp_options[option] = value + + return temp_options + + def __del__(self): + self.disconnect() + + @abstractmethod + def disconnect(self): + """Should call join with DISCONNECT_TIMEOUT, only if still alive""" + pass diff --git a/tangostationcontrol/tangostationcontrol/clients/tcp_replicator.py b/tangostationcontrol/tangostationcontrol/clients/tcp_replicator.py new file mode 100644 index 0000000000000000000000000000000000000000..8b820cc765daa193fe142f4a3f49ef7a3f643c76 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/clients/tcp_replicator.py @@ -0,0 +1,355 @@ +from queue import Empty +from queue import Queue +from threading import Condition +from threading import Semaphore +from threading import Thread +import asyncio +import logging + +from tangostationcontrol.clients.statistics_client_thread import StatisticsClientThread + +logger = logging.getLogger() + + +class TCPReplicator(Thread, StatisticsClientThread): + """TCP replicator intended to fan out incoming UDP packets + + There are three different processing layers in this class, several + methods can be called from the context of the thread that spawned this + class (main thread). These include: __init__, transmit, join. + + When constructed start is called, the thread will launch, this will call run + from the context of this new thread. This thread will create the new event + loop as this can only be done from the context of the thread you desire to + use the event loop in. A semaphore is used to prevent a potential race + between this new thread setting up the event loop and the main thread trying + to tear it down by calling join. The constructor waits on this semaphore + which will always be released either by _server_start_callback or by the + finally clause in run. + + The final layer is the event loop itself, it handles instances of the + TCPServerProtocol. These can be found in the _connected_clients list. + However, only async task are allowed to call methods on these objects! + The async methods are _transmit, _disconnect, _stop_event_loop, + _process_queue and _run_server. + + _process_queue takes elements of the queue and transmits them across clients. + It uses an asyncio.Queue to process elements, given to the replicator through + the put method. + + To cleanly shutdown this loop in _stop_event_loop, we insert a None magic marker + into the queue, causing the _process_task to return. + + Disconnecting the clients and stopping of the server is handled in _disconnect. + + """ + + """Default options for TCPReplicator + we kindly ask to not change this static variable at runtime. + """ + _default_options = { + "tcp_bind": '0.0.0.0', + "tcp_port": 6666, + "tcp_buffer_size": 128000000, # In bytes + } + + def __init__(self, options: dict = None, queuesize=0): + super().__init__() + + self.queuesize = queuesize + + # statistics + self.nof_packets_sent = 0 + self.nof_bytes_sent = 0 + + """Reserve asyncio event loop attribute but don't create it yet. + This event loop is created inside the new Thread, the result is that + the thread owns the event loop! EVENT LOOPS ARE NOT THREAD SAFE ALL + CALLS TO THE EVENT LOOP OBJECT MUST USE THE call_soon_threadsafe + FUNCTION!! + """ + self._loop = None + + # Used to maintain a reference to the server object so we can stop + # listening cleanly + self._server = None + + # Maintain a reference to the current _process_queue task so we can + # cleanly cancel it. This reduces a lot of logging chatter. + self._process_task = None + + # Create and acquire lock to prevent leaving the constructor without + # starting the thread. + self.initialization_semaphore = Semaphore() + self.initialization_semaphore.acquire() + + # Create condition to orchestrate clean disconnecting and shutdown + # They are actually the same object, just with different names for + # clarity. + self.disconnect_condition = Condition() + self.shutdown_condition = self.disconnect_condition + + # Connected clients the event loop is managing + self._connected_clients = [] + + # Parse the configured options + self.options = self._parse_options(options) + + # We start ourselves immediately to reduce amount of possible states. + self.start() + + # Wait until we can hold the semaphore, this indicates the thread has + # initialized or encountered an exception. + with self.initialization_semaphore: + if not self.is_alive(): + raise RuntimeError("TCPReplicator failed to initialize") + + logging.debug("TCPReplicator initialization completed") + + @property + def _options(self) -> dict: + return TCPReplicator._default_options + + class TCPServerProtocol(asyncio.Protocol): + """TCP protocol used for connected clients""" + + def __init__(self, options: dict, connected_clients: list): + self.options = options + + # Make connected_clients reflect the TCPReplicator connected_clients + self.connected_clients = connected_clients + + def connection_made(self, transport): + """Setup client connection and add entry to connected_clients""" + peername = transport.get_extra_info('peername') + logger.debug('TCP connection from {}'.format(peername)) + self.transport = transport + # Set the TCP buffer limit + self.transport.set_write_buffer_limits( + high=self.options['tcp_buffer_size']) + self.connected_clients.append(self) + + def pause_writing(self): + """Called when TCP buffer for the specific connection is full + + Upon encountering a full TCP buffer we deem the client to slow and + forcefully close its connection. + """ + self.transport.abort() + + def connection_lost(self, exc): + """Called when connection is lost + + Used to remove entries from connected_clients + """ + peername = self.transport.get_extra_info('peername') + logger.debug('TCP connection lost from {}'.format(peername)) + self.connected_clients.remove(self) + + def eof_received(self): + """After eof_received, connection_lost is still called""" + pass + + def run(self): + """Run is launched from constructor of TCPReplicator + + It manages an asyncio event loop to orchestrate our TCPServerProtocol. + """ + try: + logger.info("Starting TCPReplicator thread for {}:{}".format(self.options["tcp_bind"], self.options["tcp_port"])) + + # Create the event loop, must be done in the new thread + self._loop = asyncio.new_event_loop() + + # Create the input queue + self.queue = asyncio.Queue(maxsize=self.queuesize, loop=self._loop) + + # When wanting to debug event loop behavior, uncomment this + # self._loop.set_debug(True) + + self._process_task = self._loop.create_task(self._process_queue()) + + # Schedule the task to create the server + server_task = self._loop.create_task(self._run_server( + self.options, self._connected_clients)) + + # Callback monitors server startup and releases + # initialization_semaphore. If server fails to start this callback + # call self._loop.stop() + server_task.add_done_callback(self._server_start_callback) + + # Keep running event loop until self._loop.stop() is called. + # Calling this will lose control flow to the event loop + # indefinitely, upon self._loop.stop() control flow is returned + # here. + self._loop.run_forever() + + # Stop must have been called, close the event loop + with self.shutdown_condition: + logger.debug("Closing TCPReplicator event loop") + self._loop.close() + self.shutdown_condition.notify() + except Exception as e: + # Log the exception as thread exceptions won't be returned to us + # on the main thread. + logging.exception("TCPReplicator thread encountered fatal exception") + + # We will lose the exception and the original stacktrace of the + # thread. Once we use a threadpool it will be much easier to + # retrieve this so I propose to not bother implementing it now. + # For the pattern to do this see anyway: + # https://stackoverflow.com/a/6894023 + + # Due to the exception the run method will return making is_alive() + # false + finally: + # Always release the lock upon error so the constructor can return + if self.initialization_semaphore.acquire(blocking=False) is False: + self.initialization_semaphore.release() + + def transmit(self, data: bytes): + """Transmit data to connected clients""" + + if not isinstance(data, (bytes, bytearray)): + raise TypeError("Data must be byte-like object") + + self._loop.call_soon_threadsafe( + self._loop.create_task, self._transmit(data)) + + def join(self, timeout=None): + logging.info("Received shutdown request on TCPReplicator thread for {}:{}".format(self.options["tcp_bind"], self.options["tcp_port"])) + + self._clean_shutdown() + + # Only call join at the end otherwise Thread will falsely assume + # all child 'processes' have stopped + super().join(timeout) + + def disconnect(self): + if not self.is_alive(): + return + + # TODO(Corne): Prevent duplicate code across TCPReplicator, UDPReceiver + # and StatisticsCollector. + self.join(self.DISCONNECT_TIMEOUT) + + if self.is_alive(): + # there is nothing we can do except wait (stall) longer, which + # could be indefinitely. + logger.error( + f"UDP thread for {self.host}:{self.port} did not shutdown after" + f"{self.DISCONNECT_TIMEOUT} seconds, just leaving it dangling." + f"Please attach a debugger to thread ID {self.ident}.") + + async def _run_server(self, options: dict, connected_clients: list): + """Retrieve the event loop created in run() and launch the server""" + loop = asyncio.get_event_loop() + + self._server = await loop.create_server( + lambda: TCPReplicator.TCPServerProtocol(options, connected_clients), + options['tcp_bind'], options['tcp_port'], reuse_address=True) + + def put(self, packet): + """ Put a packet in the queue to be scheduled for transmission. """ + + # check hereif our queue clogged up, since we'll schedule self.queue.put + # asynchronously. + if self.queue.full(): + raise asyncio.QueueFull("asyncio queue full") + + # if we cannot process fast enough, our task list may clog up instead. + # just use the same limit here, as the task list will be dominated by the + # packet transmission count. + if self.queuesize > 0 and self.nof_tasks_pending > self.queuesize: + raise asyncio.QueueFull("asyncio loop task list full") + + self._loop.call_soon_threadsafe( + self._loop.create_task, self.queue.put(packet)) + + async def _process_queue(self): + """ Take packets from the queue and transmit them across our clients. """ + while True: + packet = await self.queue.get() + + if packet is None: + # Magic marker from caller to terminate + break + + self._loop.create_task(self._transmit(packet)) + + async def _transmit(self, data): + for client in self._connected_clients: + client.transport.write(data) + + self.nof_packets_sent += 1 + self.nof_bytes_sent += len(data) + + async def _disconnect(self): + with self.disconnect_condition: + self._server.close() + await self._server.wait_closed() + + for client in self._connected_clients: + peername = client.transport.get_extra_info('peername') + logger.debug('Disconnecting client {}'.format(peername)) + client.transport.abort() + + self.disconnect_condition.notify() + + async def _stop_event_loop(self): + with self.shutdown_condition: + + # Stop the current _process_queue task if it exists + if self._process_task: + # insert magic marker, if the caller hasn't already + await self.queue.put(None) + + # wait for task to finish + await self._process_task + + # Calling stop() will return control flow to self._loop.run_*() + self._loop.stop() + + def _server_start_callback(self, future): + # Server started without exception release initialization semaphore + if not future.exception(): + self.initialization_semaphore.release() + return + + logging.warning("TCPReplicator server raised unexpected exception") + # Stop the loop so run() can fallthrough from self._loop.run_* + self._loop.stop() + # Raise the original exceptions captured from the start_server task + raise future.exception() + + def _clean_shutdown(self): + """Disconnect clients, stop the event loop and wait for it to close""" + + # The event loop is not running anymore, we can't send tasks to shut + # it down further. + if not self._loop.is_running(): + return + + # Shutdown server and disconnect clients + with self.disconnect_condition: + self._loop.call_soon_threadsafe( + self._loop.create_task, self._disconnect()) + self.disconnect_condition.wait() + + # Stop and close the event loop + with self.shutdown_condition: + logging.debug("Stopping TCPReplicator event loop") + self._loop.call_soon_threadsafe( + self._loop.create_task, self._stop_event_loop()) + self.shutdown_condition.wait() + + def clients(self): + """ Return the list of connected clients. """ + + return ["%s:%s" % client.transport.get_extra_info('peername') for client in self._connected_clients] + + @property + def nof_tasks_pending(self): + """ Return the number of pending tasks in our event loop. """ + + return len(asyncio.all_tasks(self._loop)) diff --git a/devices/clients/udp_receiver.py b/tangostationcontrol/tangostationcontrol/clients/udp_receiver.py similarity index 65% rename from devices/clients/udp_receiver.py rename to tangostationcontrol/tangostationcontrol/clients/udp_receiver.py index 13f68f509ede31ac69c6fa0ab9b9d023cbda349b..2bda038a1fb0646af2d2e14082e10ca3d7866816 100644 --- a/devices/clients/udp_receiver.py +++ b/tangostationcontrol/tangostationcontrol/clients/udp_receiver.py @@ -1,29 +1,53 @@ +from queue import Full from queue import Queue from threading import Thread -import numpy import logging +import numpy import socket import time +from typing import List # not needed for python3.9+, where we can use the type "list[Queue]" directly + +from tangostationcontrol.clients.statistics_client_thread import StatisticsClientThread logger = logging.getLogger() -class UDPReceiver(Thread): +class UDPReceiver(Thread, StatisticsClientThread): """ This class provides a small wrapper for the OPC ua read/write functions in order to better organise the code """ - # How long to wait for a stuck Thread - DISCONNECT_TIMEOUT = 10.0 + # Default options for UDPReceiver + _default_options = { + "udp_host": None, + "udp_port": None, + "poll_timeout": 0.1, + } + + def __init__(self, queues: List[Queue], options: dict = None): + self.queues = queues + + try: + options['udp_host'] + except KeyError: + raise - def __init__(self, host, port, queue, poll_timeout=0.1): - self.queue = queue - self.host = host - self.port = port + try: + options['udp_port'] + except KeyError: + raise + + self.options = self._parse_options(options) + + self.host = self.options['udp_host'] + self.port = self.options['udp_port'] + self.poll_timeout = self.options['poll_timeout'] self.parameters = { # Number of packets we received "nof_packets_received": numpy.uint64(0), + # Number of bytes we received + "nof_bytes_received": numpy.uint64(0), # Number of packets we had to drop due to a full queue "nof_packets_dropped": numpy.uint64(0), # Packets are at most 9000 bytes, the largest payload (well, MTU) of an Ethernet Jumbo frame @@ -40,18 +64,25 @@ class UDPReceiver(Thread): # This is stock socket usage. self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + # Increase buffers to prevent data loss when our class isn't listening. + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 16*1024*1024) + # specify what host and port to listen on self.sock.bind((self.host, self.port)) # Make sure we can stop receiving packets even if none arrive. # Without this, the recvmsg() call blocks indefinitely if no packet arrives. - self.sock.settimeout(poll_timeout) + self.sock.settimeout(self.poll_timeout) self.stream_on = True super().__init__() self.start() + @property + def _options(self) -> dict: + return UDPReceiver._default_options + def run(self): # all variables are manually defined and are updated each time logger.info("Starting UDP thread for {}:{}".format(self.host, self.port)) @@ -61,15 +92,17 @@ class UDPReceiver(Thread): packet, _, _, _ = self.sock.recvmsg(9000) self.parameters["nof_packets_received"] += numpy.uint64(1) + self.parameters["nof_bytes_received"] += numpy.uint64(len(packet)) self.parameters["last_packet"] = numpy.frombuffer(packet, dtype=numpy.uint8) self.parameters["last_packet_timestamp"] = numpy.uint64(int(time.time())) - # Forward packet to processing thread - self.queue.put(packet) + # Forward packet to processing threads + for queue in self.queues: + queue.put(packet) except socket.timeout: # timeout -- expected, allows us to check whether to stop pass - except queue.Full: + except Full: # overflow -- just discard self.parameters["nof_packets_dropped"] += numpy.uint64(1) @@ -85,10 +118,12 @@ class UDPReceiver(Thread): # happens if timeout is hit return - # shutdown the socket so that others can listen on this port - self.sock.shutdown(socket.SHUT_RDWR) + # close the socket so that others can listen on this port + self.sock.close() def disconnect(self): + # TODO(Corne): Prevent duplicate code across TCPReplicator, UDPReceiver + # and StatisticsCollector. if not self.is_alive(): return @@ -98,6 +133,3 @@ class UDPReceiver(Thread): if self.is_alive(): # there is nothing we can do except wait (stall) longer, which could be indefinitely. logger.error(f"UDP thread for {self.host}:{self.port} did not shut down after {self.DISCONNECT_TIMEOUT} seconds, just leaving it dangling. Please attach a debugger to thread ID {self.ident}.") - - def __del__(self): - self.disconnect() diff --git a/devices/devices/sdp/__init__.py b/tangostationcontrol/tangostationcontrol/common/__init__.py similarity index 100% rename from devices/devices/sdp/__init__.py rename to tangostationcontrol/tangostationcontrol/common/__init__.py diff --git a/tangostationcontrol/tangostationcontrol/common/baselines.py b/tangostationcontrol/tangostationcontrol/common/baselines.py new file mode 100644 index 0000000000000000000000000000000000000000..b9b0ca8038c0d881d602df37f99203d733f283fc --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/common/baselines.py @@ -0,0 +1,59 @@ +""" + Baseline calculation functions. +""" + +import math + +def nr_baselines(nr_inputs: int) -> int: + """ Return the number of baselines (unique pairs) that exist between a given number of inputs. """ + return nr_inputs * (nr_inputs + 1) // 2 + +""" + + Baselines are ordered like: + 0-0, 1-0, 1-1, 2-0, 2-1, 2-2, ... + + if + b = baseline + x = stat1 (major) + y = stat2 (minor) + x >= y + then + b_xy = x * (x + 1) / 2 + y + let + u := b_x0 + then + u = x * (x + 1) / 2 + 8u = 4x^2 + 4x + 8u + 1 = 4x^2 + 4x + 1 = (2x + 1)^2 + sqrt(8u + 1) = 2x + 1 + x = (sqrt(8u + 1) - 1) / 2 + + Let us define + x'(b) = (sqrt(8b + 1) - 1) / 2 + which increases monotonically and is a continuation of y(b). + + Because y simply increases by 1 when b increases enough, we + can just take the floor function to obtain the discrete y(b): + x(b) = floor(x'(b)) + = floor(sqrt(8b + 1) - 1) / 2) + +""" + +def baseline_index(major: int, minor: int) -> int: + """ Provide a total ordering of baselines: give the unique array index for the baseline (major,minor), + with major >= minor. """ + + if major < minor: + raise ValueError(f"major < minor: {major} < {minor}. Since we do not store the conjugates this will lead to processing errors.") + + return major * (major + 1) // 2 + minor + +def baseline_from_index(index: int) -> tuple: + """ Return the (major,minor) input pair given a baseline index. """ + + major = int((math.sqrt(float(8 * index + 1)) - 0.99999) / 2) + minor = index - baseline_index(major,0) + + return (major,minor) + diff --git a/tangostationcontrol/tangostationcontrol/common/entrypoint.py b/tangostationcontrol/tangostationcontrol/common/entrypoint.py new file mode 100644 index 0000000000000000000000000000000000000000..d5b9d4952ee08180162b3bdca7417fff31f456f0 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/common/entrypoint.py @@ -0,0 +1,21 @@ +import sys + +from tango.server import run + +from tangostationcontrol.common.lofar_logging import configure_logger + + +def entry(Device, **kwargs): + """General device entrypoint""" + + # Remove first argument which is filename + args = sys.argv[1:] + + # Setup logging + configure_logger() + + # Start the device server + if type(Device) is tuple: + return run(Device, args=args, **kwargs) + else: + return run((Device,), args=args, **kwargs) diff --git a/devices/common/lofar_logging.py b/tangostationcontrol/tangostationcontrol/common/lofar_logging.py similarity index 71% rename from devices/common/lofar_logging.py rename to tangostationcontrol/tangostationcontrol/common/lofar_logging.py index aed0353461d75ae6ad46b4b10ad51289fb08b553..cfff61966f11a06c2fcf0e336da32eba24bfbfe2 100644 --- a/devices/common/lofar_logging.py +++ b/tangostationcontrol/tangostationcontrol/common/lofar_logging.py @@ -1,11 +1,11 @@ import logging from functools import wraps from tango.server import Device -import sys import traceback import socket +import time -from .lofar_git import get_version +from .lofar_version import get_version class TangoLoggingHandler(logging.Handler): level_to_device_stream = { @@ -29,10 +29,48 @@ class TangoLoggingHandler(logging.Handler): stream = self.level_to_device_stream[record.levelno] # send the log message to Tango - stream(record.tango_device, record.msg, *record.args) + try: + record_msg = record.msg % record.args + stream(record.tango_device, record.msg, *record.args) + except TypeError: + # Tango's logger barfs on mal-formed log lines, f.e. if msg % args is not possible + record_msg = f"{record.msg} {record.args}".replace("%","%%") + stream(record.tango_device, record_msg) self.flush() +class LogSuppressErrorSpam(logging.Formatter): + """ + Suppress specific errors from spamming the logs, by only letting them through periodically. + """ + + def __init__(self, error_suppress_interval = 3600): + """ Suppress subsequent errors for `error_suppress_interval` seconds. """ + + super().__init__() + + # last time we logged an error + self.last_error_log_time = 0 + + # suppression interval at which we report errors + self.error_suppress_interval = error_suppress_interval + + def is_error_to_suppress(self, record): + # Errors occuring by not being able to connect to the ELK stack, f.e. because it is down. + return record.name == "LogProcessingWorker" and record.msg == "An error occurred while sending events: %s" + + def filter(self, record): + if self.is_error_to_suppress(record): + # filter out error if it occurred within our suppression interval + now = time.time() + + if now - self.last_error_log_time < self.error_suppress_interval: + return False + + self.last_error_log_time = now + + return True + class LogAnnotator(logging.Formatter): """ Annotates log records with: @@ -55,13 +93,16 @@ class LogAnnotator(logging.Formatter): # annotate record with currently executing Tango device, if any record.tango_device = self.get_current_tango_device() + # construct an identifier we can add for other devices as well + record.lofar_id = f"tango - {record.tango_device}" + # annotate record with the current software version record.software_version = get_version() # we just annotate, we don't filter return True -def configure_logger(logger: logging.Logger=None, log_extra=None): +def configure_logger(logger: logging.Logger=None, log_extra=None, debug=False): """ Configure the given logger (or root if None) to: - send logs to the ELK stack @@ -79,18 +120,42 @@ def configure_logger(logger: logging.Logger=None, log_extra=None): logger.setLevel(logging.DEBUG) # remove spam from the OPC-UA client connection - logging.getLogger("opcua").setLevel(logging.WARN) + logging.getLogger("asyncua").setLevel(logging.WARN) + + # don't spam errors for git, as we use it in our log handler, which would result in an infinite loop + logging.getLogger("git").setLevel(logging.ERROR) + + # for now, also log to stderr + # Set up logging in a way that it can be understood by a human reader, be + # easily grep'ed, be parsed with a couple of shell commands and + # easily fed into an Kibana/Elastic search system. + handler = logging.StreamHandler() + + # Always also log the hostname because it makes the origin of the log clear. + hostname = socket.gethostname() + + formatter = logging.Formatter(fmt = '%(asctime)s.%(msecs)d %(levelname)s - HOST="{}" DEVICE="%(tango_device)s" PID="%(process)d" TNAME="%(threadName)s" FILE="%(pathname)s" LINE="%(lineno)d" FUNC="%(funcName)s" MSG="%(message)s"'.format(hostname), datefmt = '%Y-%m-%dT%H:%M:%S') + handler.setFormatter(formatter) + handler.addFilter(LogSuppressErrorSpam()) + handler.addFilter(LogAnnotator()) + + logger.addHandler(handler) + + # If configuring for debug; exit early + if debug: + return logger # Log to ELK stack try: from logstash_async.handler import AsynchronousLogstashHandler, LogstashFormatter # log to the tcp_input of logstash in our ELK stack - handler = AsynchronousLogstashHandler("elk", 5959, database_path='pending_log_messages.db') + handler = AsynchronousLogstashHandler("elk", 5959, database_path='/tmp/lofar_pending_log_messages.db') # configure log messages formatter = LogstashFormatter(extra=log_extra, tags=["python", "lofar"]) handler.setFormatter(formatter) + handler.addFilter(LogSuppressErrorSpam()) handler.addFilter(LogAnnotator()) # install the handler @@ -103,27 +168,12 @@ def configure_logger(logger: logging.Logger=None, log_extra=None): # Log to Tango try: handler = TangoLoggingHandler() + handler.addFilter(LogSuppressErrorSpam()) handler.addFilter(LogAnnotator()) logger.addHandler(handler) except Exception: logger.exception("Cannot forward logs to Tango.") - - # for now, also log to stderr - # Set up logging in a way that it can be understood by a human reader, be - # easily grep'ed, be parsed with a couple of shell commands and - # easily fed into an Kibana/Elastic search system. - handler = logging.StreamHandler() - - # Always also log the hostname because it makes the origin of the log clear. - hostname = socket.gethostname() - - formatter = logging.Formatter(fmt = '%(asctime)s.%(msecs)d %(levelname)s - HOST="{}" DEVICE="%(tango_device)s" PID="%(process)d" TNAME="%(threadName)s" FILE="%(pathname)s" LINE="%(lineno)d" FUNC="%(funcName)s" MSG="%(message)s"'.format(hostname), datefmt = '%Y-%m-%dT%H:%M:%S') - handler.setFormatter(formatter) - handler.addFilter(LogAnnotator()) - - logger.addHandler(handler) - return logger def device_logging_to_python(): diff --git a/tangostationcontrol/tangostationcontrol/common/lofar_version.py b/tangostationcontrol/tangostationcontrol/common/lofar_version.py new file mode 100644 index 0000000000000000000000000000000000000000..89cb22f9fe6b76caf438984c673b21d00bc25645 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/common/lofar_version.py @@ -0,0 +1,103 @@ +import git +import os +import functools +import pkg_resources +import re + +def get_repo(starting_directory: str = os.path.dirname(os.path.abspath(__file__)), limit = 10) -> git.Repo: + """ Try finding the repository by traversing up the tree. + + By default, the repository containing this module is returned. + """ + + directory = starting_directory + + try: + return git.Repo(directory) + except git.InvalidGitRepositoryError: + pass + + # We now have to traverse up the tree up until limit diretories + for i in range(limit): + if directory == "/" or not os.path.exists(directory): + break + + directory = os.path.abspath(directory + os.path.sep + "..") + + try: + return git.Repo(directory) + except git.InvalidGitRepositoryError: + pass + + # Could not find a repo within the limit so return None + return None + + +@functools.lru_cache(maxsize=None) +def get_version(repo: git.Repo = None) -> str: + """ Return a version string for the current commit. + + There is a practical issue: the repository changes over time, f.e. switching branches with 'git checkout'. We want + to know the version that is running in memory, not the one that is on disk. + + As a work-around, we cache the version information, in that it is at least consistent. It is up to the caller + to request the version early enough. + + The version string is of the following pattern: + - ${MAJOR}.${MINOR}.${PATCH}[.${BRANCH}$.{COMMIT}][.dirty] + + For releases only ${MAJOR}.${MINOR}.${PATCH} should be set. Versioning is + achieved by tagging commits using the `v${MAJOR}.${MINOR}.${PATCH}` pattern. + The leading `v` is none optional! + + """ + + if repo is None: + repo = get_repo() + + # When we can't find a git repo anymore, we must be packaged. Extract the + # package version directly + if repo is None: + try: + return pkg_resources.require("tangostationcontrol")[0].version + except Exception: + pass + + # Filter all tags so that they must match vMAJOR.MINOR.PATCH or + # vMAJOR.MINOR.PATCH.BRANCHCOMMIT + reg = re.compile(r'^v[0-9](\.[0-9]){2}(\.[a-z]*[0-9]*)?') + + commit = repo.commit() + filtered_tags = [tag.name for tag in repo.tags if reg.search(tag.name)] + # Order tags from newest to oldest + tags = {tag.commit: tag for tag in reversed(repo.tags) if tag.name in filtered_tags} + + # Find closest tag for commit + closest_tag = type('',(object,),{"name": 'v0.0.0'})() + for item in commit.iter_items(repo, commit): + if item.type == 'commit' and item in tags: + closest_tag = tags[item] + break + + if commit in tags: + # a tag = production ready + commit_str = "{}".format(tags[commit].name[1:]) + elif repo.head.is_detached: + # no active branch + commit_str = "{}.{}".format(closest_tag.name[1:], commit) + else: + # HEAD of a branch + branch = repo.active_branch + commit_str = "{}.{}.{}".format(closest_tag.name[1:], branch, commit) + + return "{}{}".format(commit_str, ".dirty" if repo.is_dirty() else "") + +# at least cache the current repo version immediately +try: + _ = get_version() +except: + pass + + +def main(args=None, **kwargs): + print(get_version()) diff --git a/devices/examples/__init__.py b/tangostationcontrol/tangostationcontrol/devices/__init__.py similarity index 100% rename from devices/examples/__init__.py rename to tangostationcontrol/tangostationcontrol/devices/__init__.py diff --git a/tangostationcontrol/tangostationcontrol/devices/abstract_device.py b/tangostationcontrol/tangostationcontrol/devices/abstract_device.py new file mode 100644 index 0000000000000000000000000000000000000000..8250e4e481dc9d27ec88654b6fef777d0e9bc4e4 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/abstract_device.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the XXX project +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +"""Abstract Device Meta for LOFAR2.0 + +""" + +from abc import ABCMeta +import logging + +from tango.server import DeviceMeta + +logger = logging.getLogger() + + +class AbstractDeviceMetas(DeviceMeta, ABCMeta): + """Collects meta classes to allow lofar_device to be both a Device and an ABC. """ + + def __new__(mcs, name, bases, namespace, **kwargs): + cls = ABCMeta.__new__(mcs, name, bases, namespace, **kwargs) + cls = DeviceMeta.__new__(type(cls), name, bases, namespace) + return cls diff --git a/tangostationcontrol/tangostationcontrol/devices/apsct.py b/tangostationcontrol/tangostationcontrol/devices/apsct.py new file mode 100644 index 0000000000000000000000000000000000000000..add4e146cdb5fb4282a49f9394c552465b088a26 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/apsct.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the RECV project +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" APSCT Device Server for LOFAR2.0 + +""" + +# PyTango imports +from tango import DebugIt +from tango.server import run, command +from tango import AttrWriteType, DevState +import numpy +# Additional import + +from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper +from tangostationcontrol.common.entrypoint import entry +from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions +from tangostationcontrol.devices.device_decorators import * +from tangostationcontrol.devices.opcua_device import opcua_device + +import logging +logger = logging.getLogger() + +__all__ = ["APSCT", "main"] + + +@device_logging_to_python() +class APSCT(opcua_device): + # ----------------- + # Device Properties + # ----------------- + + # ---------- + # Attributes + # ---------- + + APSCTTR_I2C_error_R = attribute_wrapper(comms_annotation=["APSCTTR_I2C_error_R" ],datatype=numpy.int64 ) + APSCTTR_monitor_rate_RW = attribute_wrapper(comms_annotation=["APSCTTR_monitor_rate_RW" ],datatype=numpy.int64 , access=AttrWriteType.READ_WRITE) + APSCTTR_translator_busy_R = attribute_wrapper(comms_annotation=["APSCTTR_translator_busy_R" ],datatype=numpy.bool_ ) + APSCT_INPUT_10MHz_good_R = attribute_wrapper(comms_annotation=["APSCT_INPUT_10MHz_good_R" ],datatype=numpy.bool_ ) + APSCT_INPUT_PPS_good_R = attribute_wrapper(comms_annotation=["APSCT_INPUT_PPS_good_R" ],datatype=numpy.bool_ ) + APSCT_PCB_ID_R = attribute_wrapper(comms_annotation=["APSCT_PCB_ID_R" ],datatype=numpy.int64 ) + APSCT_PCB_number_R = attribute_wrapper(comms_annotation=["APSCT_PCB_number_R" ],datatype=numpy.str ) + APSCT_PCB_version_R = attribute_wrapper(comms_annotation=["APSCT_PCB_version_R" ],datatype=numpy.str ) + APSCT_PLL_160MHz_error_R = attribute_wrapper(comms_annotation=["APSCT_PLL_160MHz_error_R" ],datatype=numpy.bool_ ) + APSCT_PLL_160MHz_locked_R = attribute_wrapper(comms_annotation=["APSCT_PLL_160MHz_locked_R" ],datatype=numpy.bool_ ) + APSCT_PLL_200MHz_error_R = attribute_wrapper(comms_annotation=["APSCT_PLL_200MHz_error_R" ],datatype=numpy.bool_ ) + APSCT_PLL_200MHz_locked_R = attribute_wrapper(comms_annotation=["APSCT_PLL_200MHz_locked_R" ],datatype=numpy.bool_ ) + APSCT_PPS_ignore_R = attribute_wrapper(comms_annotation=["APSCT_PPS_ignore_R" ],datatype=numpy.bool_ ) + APSCT_PPS_ignore_RW = attribute_wrapper(comms_annotation=["APSCT_PPS_ignore_RW" ],datatype=numpy.bool_ , access=AttrWriteType.READ_WRITE) + APSCT_PWR_CLKDIST1_3V3_R = attribute_wrapper(comms_annotation=["APSCT_PWR_CLKDIST1_3V3_R" ],datatype=numpy.float64) + APSCT_PWR_CLKDIST2_3V3_R = attribute_wrapper(comms_annotation=["APSCT_PWR_CLKDIST2_3V3_R" ],datatype=numpy.float64) + APSCT_PWR_CTRL_3V3_R = attribute_wrapper(comms_annotation=["APSCT_PWR_CTRL_3V3_R" ],datatype=numpy.float64) + APSCT_PWR_INPUT_3V3_R = attribute_wrapper(comms_annotation=["APSCT_PWR_INPUT_3V3_R" ],datatype=numpy.float64) + APSCT_PWR_on_R = attribute_wrapper(comms_annotation=["APSCT_PWR_on_R" ],datatype=numpy.bool_ ) + APSCT_PWR_PLL_160MHz_3V3_R = attribute_wrapper(comms_annotation=["APSCT_PWR_PLL_160MHz_3V3_R"],datatype=numpy.float64) + APSCT_PWR_PLL_160MHz_on_R = attribute_wrapper(comms_annotation=["APSCT_PWR_PLL_160MHz_on_R" ],datatype=numpy.bool_ ) + APSCT_PWR_PLL_200MHz_3V3_R = attribute_wrapper(comms_annotation=["APSCT_PWR_PLL_200MHz_3V3_R"],datatype=numpy.float64) + APSCT_PWR_PLL_200MHz_on_R = attribute_wrapper(comms_annotation=["APSCT_PWR_PLL_200MHz_on_R" ],datatype=numpy.bool_ ) + APSCT_PWR_PPSDIST_3V3_R = attribute_wrapper(comms_annotation=["APSCT_PWR_PPSDIST_3V3_R" ],datatype=numpy.float64) + APSCT_TEMP_R = attribute_wrapper(comms_annotation=["APSCT_TEMP_R" ],datatype=numpy.float64) + + # -------- + # overloaded functions + # -------- + + def _initialise_hardware(self): + """ Initialise the APSCT hardware. """ + + # method calls don't work yet, so don't use them to allow the boot + # device to initialise us without errors + logger.error("OPC-UA methods not supported yet, not initialising APSCT hardware!") + return + + # Cycle clock + self.CLK_off() + self.wait_attribute("APSCTTR_translator_busy_R", False, 10) + self.CLK_on() + self.wait_attribute("APSCTTR_translator_busy_R", False, 10) + + if not self.APSCT_PLL_200MHz_locked_R: + if self.APSCT_I2C_error_R: + raise Exception("I2C is not working. Maybe power cycle subrack to restart CLK board and translator?") + else: + raise Exception("200MHz signal is not locked. The subrack probably do not receive clock input or the CLK PCB is broken?") + + # -------- + # Commands + # -------- + + @command() + @DebugIt() + @only_when_on() + def APSCT_off(self): + """ + + :return:None + """ + self.opcua_connection.call_method(["APSCT_off"]) + + @command() + @DebugIt() + @only_when_on() + def APSCT_on(self): + """ + + :return:None + """ + self.opcua_connection.call_method(["APSCT_on"]) + + +# ---------- +# Run server +# ---------- +def main(**kwargs): + """Main function of the ObservationControl module.""" + return entry(APSCT, **kwargs) diff --git a/tangostationcontrol/tangostationcontrol/devices/apspu.py b/tangostationcontrol/tangostationcontrol/devices/apspu.py new file mode 100644 index 0000000000000000000000000000000000000000..88a677fcb1cd743e1e59f8fe3aecbbb2a0992cba --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/apspu.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the RECV project +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" APSPU Device Server for LOFAR2.0 + +""" + +# PyTango imports +from tango import AttrWriteType +import numpy +# Additional import + +from tangostationcontrol.devices.device_decorators import * +from tangostationcontrol.common.entrypoint import entry +from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper +from tangostationcontrol.devices.opcua_device import opcua_device +from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions + +__all__ = ["APSPU", "main"] + + +@device_logging_to_python() +class APSPU(opcua_device): + # ----------------- + # Device Properties + # ----------------- + + # ---------- + # Attributes + # ---------- + + APSPUTR_I2C_error_R = attribute_wrapper(comms_annotation=["APSPUTR_I2C_error_R" ],datatype=numpy.int64 ) + APSPUTR_monitor_rate_RW = attribute_wrapper(comms_annotation=["APSPUTR_monitor_rate_RW" ],datatype=numpy.int64 , access=AttrWriteType.READ_WRITE) + APSPUTR_translator_busy_R = attribute_wrapper(comms_annotation=["APSPUTR_translator_busy_R" ],datatype=numpy.bool_ ) + APSPU_FAN1_RPM_R = attribute_wrapper(comms_annotation=["APSPU_FAN1_RPM_R" ],datatype=numpy.float64) + APSPU_FAN2_RPM_R = attribute_wrapper(comms_annotation=["APSPU_FAN2_RPM_R" ],datatype=numpy.float64) + APSPU_FAN3_RPM_R = attribute_wrapper(comms_annotation=["APSPU_FAN3_RPM_R" ],datatype=numpy.float64) + APSPU_LBA_IOUT_R = attribute_wrapper(comms_annotation=["APSPU_LBA_IOUT_R" ],datatype=numpy.float64) + APSPU_LBA_TEMP_R = attribute_wrapper(comms_annotation=["APSPU_LBA_TEMP_R" ],datatype=numpy.float64) + APSPU_LBA_VOUT_R = attribute_wrapper(comms_annotation=["APSPU_LBA_VOUT_R" ],datatype=numpy.float64) + APSPU_PCB_ID_R = attribute_wrapper(comms_annotation=["APSPU_PCB_ID_R" ],datatype=numpy.int64 ) + APSPU_PCB_number_R = attribute_wrapper(comms_annotation=["APSPU_PCB_number_R" ],datatype=numpy.str ) + APSPU_PCB_version_R = attribute_wrapper(comms_annotation=["APSPU_PCB_version_R" ],datatype=numpy.str ) + APSPU_RCU2A_IOUT_R = attribute_wrapper(comms_annotation=["APSPU_RCU2A_IOUT_R" ],datatype=numpy.float64) + APSPU_RCU2A_TEMP_R = attribute_wrapper(comms_annotation=["APSPU_RCU2A_TEMP_R" ],datatype=numpy.float64) + APSPU_RCU2A_VOUT_R = attribute_wrapper(comms_annotation=["APSPU_RCU2A_VOUT_R" ],datatype=numpy.float64) + APSPU_RCU2D_IOUT_R = attribute_wrapper(comms_annotation=["APSPU_RCU2D_IOUT_R" ],datatype=numpy.float64) + APSPU_RCU2D_TEMP_R = attribute_wrapper(comms_annotation=["APSPU_RCU2D_TEMP_R" ],datatype=numpy.float64) + APSPU_RCU2D_VOUT_R = attribute_wrapper(comms_annotation=["APSPU_RCU2D_VOUT_R" ],datatype=numpy.float64) + + # -------- + # overloaded functions + # -------- + + + # -------- + # Commands + # -------- + + +# ---------- +# Run server +# ---------- +def main(**kwargs): + """Main function of the ObservationControl module.""" + return entry(APSPU, **kwargs) diff --git a/tangostationcontrol/tangostationcontrol/devices/boot.py b/tangostationcontrol/tangostationcontrol/devices/boot.py new file mode 100644 index 0000000000000000000000000000000000000000..5b0acd2a587f6076e3a416151a30369a8a924478 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/boot.py @@ -0,0 +1,292 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the RECV project +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" Boot Device Server for LOFAR2.0 + +Boots the rest of the station software. + +""" + +# PyTango imports +from tango import DebugIt +from tango.server import run, command +from tango.server import device_property, attribute +from tango import AttrWriteType, DeviceProxy, DevState +# Additional import +import numpy + +from tangostationcontrol.devices.device_decorators import * + +from tangostationcontrol.common.entrypoint import entry +from tangostationcontrol.devices.lofar_device import lofar_device +from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions + +import logging +logger = logging.getLogger() + +from threading import Thread + +__all__ = ["Boot", "main"] + + +class InitialisationException(Exception): + pass + +class DevicesInitialiser(Thread): + """ + Initialise devices on this station. + + Devices which are unreachable are assumed to be brought down explicitly, + and are ignored (unless ignore_unavailable_devices == False). + + Initialisation happens in a separate thread. It is started by calling + the start() method, and progress can be followed by inspecting the + members progress (0-100), status (string), and is_running() (bool). + """ + def __init__(self, device_names, ignore_unavailable_devices=True, proxy_timeout=10.0): + self.ignore_unavailable_devices = ignore_unavailable_devices + + self.device_names = device_names + self.proxy_timeout = proxy_timeout + + # setup initial state + self.progress = 0 + self.set_status("Initialisation not started yet") + + super().__init__() + + def run(self): + self.set_status("Starting initialisation") + + try: + # Since Python3.7+, the insertion order equals the iteration order, which is what we depend on + # to process the devices in the same order as in device_names. + self.devices = {} + for name in self.device_names: + self.set_status(f"Obtaining a DeviceProxy to {name}") + self.devices[name] = DeviceProxy(name) + + # set the timeout for all proxies + self.set_status("Configuring DeviceProxies") + for device in self.devices.values(): + device.set_timeout_millis(int(self.proxy_timeout * 1000)) + + self.set_status("Initialisation started") + self.initialise_devices() + + self.set_status("Initialisation completed") + except Exception as e: + logger.exception("Failed to initialise station") + + # add the exception to the status + self.set_status(f"ERROR: {self.status} [{e.__class__.__name__}: {str(e)}]") + + # we keep the status stuck at the last thing it tried + + def is_running(self): + return self.is_alive() + + + def stop(self): + if not self.is_alive(): + return + + # Just wait for the current initialisation to finish. It's a finite process. + self.join() + + def set_status(self, status): + self.status = status + + logger.info(status) + + def initialise_devices(self): + """ + Initialise or re-initialise all devices on the station. + + :return:None + """ + + # reset initialisation parameters + self.progress = 0 + + # restart devices in order + for num_restarted_devices, device in enumerate(self.devices.keys(), 1): + if self.is_available(device) or not self.ignore_unavailable_devices: + self.start_device(device) + + self.progress = 100.0 * num_restarted_devices / len(self.devices) + + # make sure we always finish at 100% in case of success + self.progress = 100 + + def is_available(self, device_name: str): + """ Return whether the device 'device_name' is actually available on this server. """ + + proxy = self.devices[device_name] + try: + proxy.state() + except Exception as e: + return False + + return True + + def stop_device(self, device_name: str): + """ Stop device 'device_name'. """ + + proxy = self.devices[device_name] + + if proxy.state() == DevState.OFF: + # already off + return + + self.set_status(f"[stopping {device_name}] Stopping device.") + + proxy.Off() + if proxy.state() != DevState.OFF: + raise InitialisationException(f"Could not turn off device {device_name}. Please look at its logs.") + + self.set_status(f"[stopping {device_name}] Stopped device.") + + def start_device(self, device_name: str): + """ Run the startup sequence for device 'device_name'. """ + + proxy = self.devices[device_name] + + # go to a well-defined state, which may be needed if the user calls + # this function explicitly. + self.stop_device(device_name) + + # setup connections to hardware + self.set_status(f"[restarting {device_name}] Initialising device.") + proxy.Initialise() + if proxy.state() != DevState.STANDBY: + raise InitialisationException(f"Could not initialise device {device_name}. Please look at its logs.") + + # configure the device + self.set_status(f"[restarting {device_name}] Setting defaults.") + proxy.set_defaults() + + self.set_status(f"[restarting {device_name}] Initialising hardware.") + proxy.initialise_hardware() + + # mark as ready for service + self.set_status(f"[restarting {device_name}] Turning on device.") + proxy.On() + if proxy.state() != DevState.ON: + raise InitialisationException(f"Could not turn on device {device_name}. Please look at its logs.") + + self.set_status(f"[restarting {device_name}] Succesfully started.") + +@device_logging_to_python() +class Boot(lofar_device): + # ----------------- + # Device Properties + # ----------------- + + DeviceProxy_Time_Out = device_property( + dtype='DevDouble', + mandatory=False, + default_value=10.0, + ) + + # Which devices to initialise, and in which order + Device_Names = device_property( + dtype='DevVarStringArray', + mandatory=False, + default_value=["STAT/Docker/1", # Docker controls the device containers, so it goes before anything else + "STAT/APSPU/1", # APS Power Units control other hardware we want to initialise + "STAT/APSCT/1", + "STAT/RECV/1", # RCUs are input for SDP, so initialise them first + "STAT/UNB2/1", # Uniboards host SDP, so initialise them first + "STAT/SDP/1", # SDP controls the mask for SST/XST/BST, so initialise it first + "STAT/SST/1", + "STAT/XST/1", + ], + ) + + # By default, we assume any device is not available + # because its docker container was not started, which + # is an explicit and thus intentional action. + # We ignore such devices when initialising the station. + Ignore_Unavailable_Devices = device_property( + dtype='DevBoolean', + mandatory=False, + default_value=True, + ) + + # ---------- + # Attributes + # ---------- + initialising_station_R = attribute(dtype=numpy.bool_, access=AttrWriteType.READ, fget=lambda self: self.initialiser.is_running()) + initialisation_progress_R = attribute(dtype=numpy.int, access=AttrWriteType.READ, fget=lambda self: numpy.int(self.initialiser.progress)) + initialisation_status_R = attribute(dtype=str, access=AttrWriteType.READ, fget=lambda self: self.initialiser.status) + + @log_exceptions() + def delete_device(self): + """Hook to delete resources allocated in init_device. + + This method allows for any memory or other resources allocated in the + init_device method to be released. This method is called by the device + destructor and by the device Init command (a Tango built-in). + """ + logger.debug("Shutting down...") + + self.Off() + logger.debug("Shut down. Good bye.") + + # -------- + # overloaded functions + # -------- + @log_exceptions() + def configure_for_off(self): + """ user code here. is called when the state is set to OFF """ + try: + self.initialiser.stop() + except Exception as e: + logger.warning("Exception while stopping OPC ua connection in configure_for_off function: {}. Exception ignored".format(e)) + + @log_exceptions() + def configure_for_initialise(self): + # create an initialiser object so we can query it even before starting the (first) initialisation + self.initialiser = DevicesInitialiser(self.Device_Names, self.Ignore_Unavailable_Devices, self.DeviceProxy_Time_Out) + + @command() + @DebugIt() + @only_in_states([DevState.ON]) + @fault_on_error() + @log_exceptions() + def initialise_station(self): + """ + Initialise or re-initialise all devices on the station. + + This command will take a while to execute, so should be called asynchronously. + + :return:None + """ + + if self.initialiser.is_running(): + # already initialising + return + + # join any previous attempt, if any + try: + self.initialiser.join() + except RuntimeError: + pass + + # start new initialisation attempt + self.initialiser = DevicesInitialiser(self.Device_Names, self.Ignore_Unavailable_Devices, self.DeviceProxy_Time_Out) + self.initialiser.start() + +# ---------- +# Run server +# ---------- +def main(**kwargs): + """Main function of the Boot module.""" + return entry(Boot, **kwargs) diff --git a/devices/devices/device_decorators.py b/tangostationcontrol/tangostationcontrol/devices/device_decorators.py similarity index 82% rename from devices/devices/device_decorators.py rename to tangostationcontrol/tangostationcontrol/devices/device_decorators.py index 5300ba2a06380599a3457c1624344243b7f5db60..b3f203bfff1fec77efbc0b2d95d8c464a97dcb71 100644 --- a/devices/devices/device_decorators.py +++ b/tangostationcontrol/tangostationcontrol/devices/device_decorators.py @@ -2,6 +2,9 @@ from tango import DevState, Except from functools import wraps import traceback +import logging +logger = logging.getLogger() + __all__ = ["only_in_states", "only_when_on", "fault_on_error"] def only_in_states(allowed_states): @@ -15,7 +18,7 @@ def only_in_states(allowed_states): if self.get_state() in allowed_states: return func(self, *args, **kwargs) - self.warn_stream("Illegal command: Function %s can only be called in states %s. Current state: %s" % (func.__name__, allowed_states, self.get_state())) + logger.warning("Illegal command: Function %s can only be called in states %s. Current state: %s" % (func.__name__, allowed_states, self.get_state())) Except.throw_exception("IllegalCommand", "Function can only be called in states %s. Current state: %s" % (allowed_states, self.get_state()), func.__name__) return state_check_wrapper @@ -50,8 +53,10 @@ def fault_on_error(): try: return func(self, *args, **kwargs) except Exception as e: - self.error_stream("Function failed. Trace: %s", traceback.format_exc()) - self.Fault() + + logger.exception("Function failed.") + self.Fault(f"FAULT in {func.__name__}: {e.__class__.__name__}: {e}") + return None return error_wrapper diff --git a/tangostationcontrol/tangostationcontrol/devices/docker_device.py b/tangostationcontrol/tangostationcontrol/devices/docker_device.py new file mode 100644 index 0000000000000000000000000000000000000000..00f42ca816e1e8219cde06913ef633961a15366e --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/docker_device.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the Docker project +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" Docker Device Server for LOFAR2.0 + +""" + +# PyTango imports +from tango.server import run, command +from tango.server import device_property, attribute +from tango import AttrWriteType +import numpy +import asyncio +# Additional import + +from tangostationcontrol.devices.device_decorators import * + +# Additional import +from tangostationcontrol.common.entrypoint import entry +from tangostationcontrol.clients.docker_client import DockerClient +from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper +from tangostationcontrol.devices.lofar_device import lofar_device +from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions + +import logging +logger = logging.getLogger() + +__all__ = ["Docker", "main"] + +@device_logging_to_python() +class Docker(lofar_device): + # ----------------- + # Device Properties + # ----------------- + + Docker_Base_URL = device_property( + dtype='DevString', + mandatory=False, + default_value="unix:///var/run/docker.sock" + ) + + # ---------- + # Attributes + # ---------- + archiver_maria_db_R = attribute_wrapper(comms_annotation={"container": "archiver-maria-db"}, datatype=numpy.bool_) + archiver_maria_db_RW = attribute_wrapper(comms_annotation={"container": "archiver-maria-db"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + databaseds_R = attribute_wrapper(comms_annotation={"container": "databaseds"}, datatype=numpy.bool_) + databaseds_RW = attribute_wrapper(comms_annotation={"container": "databaseds"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + device_apsct_R = attribute_wrapper(comms_annotation={"container": "device-apsct"}, datatype=numpy.bool_) + device_apsct_RW = attribute_wrapper(comms_annotation={"container": "device-apsct"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + device_apspu_R = attribute_wrapper(comms_annotation={"container": "device-apspu"}, datatype=numpy.bool_) + device_apspu_RW = attribute_wrapper(comms_annotation={"container": "device-apspu"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + device_recv_R = attribute_wrapper(comms_annotation={"container": "device-recv"}, datatype=numpy.bool_) + device_recv_RW = attribute_wrapper(comms_annotation={"container": "device-recv"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + device_sdp_R = attribute_wrapper(comms_annotation={"container": "device-sdp"}, datatype=numpy.bool_) + device_sdp_RW = attribute_wrapper(comms_annotation={"container": "device-sdp"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + device_sst_R = attribute_wrapper(comms_annotation={"container": "device-sst"}, datatype=numpy.bool_) + device_sst_RW = attribute_wrapper(comms_annotation={"container": "device-sst"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + device_xst_R = attribute_wrapper(comms_annotation={"container": "device-xst"}, datatype=numpy.bool_) + device_xst_RW = attribute_wrapper(comms_annotation={"container": "device-xst"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + device_unb2_R = attribute_wrapper(comms_annotation={"container": "device-unb2"}, datatype=numpy.bool_) + device_unb2_RW = attribute_wrapper(comms_annotation={"container": "device-unb2"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + device_docker_R = attribute_wrapper(comms_annotation={"container": "device-docker"}, datatype=numpy.bool_) + # device_docker_RW is not available, as we cannot start our own container` + dsconfig_R = attribute_wrapper(comms_annotation={"container": "dsconfig"}, datatype=numpy.bool_) + dsconfig_RW = attribute_wrapper(comms_annotation={"container": "dsconfig"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + elk_R = attribute_wrapper(comms_annotation={"container": "elk"}, datatype=numpy.bool_) + elk_RW = attribute_wrapper(comms_annotation={"container": "elk"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + grafana_R = attribute_wrapper(comms_annotation={"container": "grafana"}, datatype=numpy.bool_) + grafana_RW = attribute_wrapper(comms_annotation={"container": "grafana"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + hdbpp_cm_R = attribute_wrapper(comms_annotation={"container": "hdbpp-cm"}, datatype=numpy.bool_) + hdbpp_cm_RW = attribute_wrapper(comms_annotation={"container": "hdbpp-cm"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + hdbpp_es_R = attribute_wrapper(comms_annotation={"container": "hdbpp-es"}, datatype=numpy.bool_) + hdbpp_es_RW = attribute_wrapper(comms_annotation={"container": "hdbpp-es"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + itango_R = attribute_wrapper(comms_annotation={"container": "itango"}, datatype=numpy.bool_) + itango_RW = attribute_wrapper(comms_annotation={"container": "itango"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + jupyter_R = attribute_wrapper(comms_annotation={"container": "jupyter"}, datatype=numpy.bool_) + jupyter_RW = attribute_wrapper(comms_annotation={"container": "jupyter"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + prometheus_R = attribute_wrapper(comms_annotation={"container": "prometheus"}, datatype=numpy.bool_) + prometheus_RW = attribute_wrapper(comms_annotation={"container": "prometheus"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + tangodb_R = attribute_wrapper(comms_annotation={"container": "tangodb"}, datatype=numpy.bool_) + tangodb_RW = attribute_wrapper(comms_annotation={"container": "tangodb"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + tango_prometheus_exporter_R = attribute_wrapper(comms_annotation={"container": "tango-prometheus-exporter"}, datatype=numpy.bool_) + tango_prometheus_exporter_RW = attribute_wrapper(comms_annotation={"container": "tango-prometheus-exporter"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + tango_rest_R = attribute_wrapper(comms_annotation={"container": "tango-rest"}, datatype=numpy.bool_) + tango_rest_RW = attribute_wrapper(comms_annotation={"container": "tango-rest"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + + # -------- + # overloaded functions + # -------- + @log_exceptions() + def configure_for_off(self): + """ user code here. is called when the state is set to OFF """ + # Stop keep-alive + try: + self.docker_client.sync_stop() + except Exception as e: + logger.warning("Exception while stopping docker client in configure_for_off function: {}. Exception ignored".format(e)) + + @log_exceptions() + def configure_for_initialise(self): + """ user code here. is called when the state is set to INIT """ + + # set up the Docker client + self.docker_client = DockerClient(self.Docker_Base_URL, self.Fault) + + # schedule the docker initialisation, and wait for it to finish + future = asyncio.run_coroutine_threadsafe(self._connect_docker(), self.docker_client.event_loop) + _ = future.result() + + async def _connect_docker(self): + # tie attributes to client + for i in self.attr_list(): + await i.async_set_comm_client(self.docker_client) + + await self.docker_client.start() + + # -------- + # Commands + # -------- + + +# ---------- +# Run server +# ---------- +def main(**kwargs): + """Main function of the Docker module.""" + return entry(Docker, **kwargs) diff --git a/tangostationcontrol/tangostationcontrol/devices/lofar_device.py b/tangostationcontrol/tangostationcontrol/devices/lofar_device.py new file mode 100644 index 0000000000000000000000000000000000000000..8886abd8a40b38df1fa9ec181783d9f4a2ff08ae --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/lofar_device.py @@ -0,0 +1,293 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the XXX project +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +"""Hardware Device Server for LOFAR2.0 + +""" + +from abc import abstractmethod + +# PyTango imports +from tango.server import Device, command, DeviceMeta, attribute +from tango import AttrWriteType, DevState, DebugIt, Attribute, DeviceProxy +import time +import math + +# Additional import +from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper +from tangostationcontrol.common.lofar_logging import log_exceptions +from tangostationcontrol.common.lofar_version import get_version +from tangostationcontrol.devices.abstract_device import AbstractDeviceMetas +from tangostationcontrol.devices.device_decorators import only_in_states, fault_on_error + + +__all__ = ["lofar_device"] + +import logging +logger = logging.getLogger() + +class lofar_device(Device, metaclass=AbstractDeviceMetas): + """ + + **Properties:** + + States are as follows: + INIT = Device is initialising. + STANDBY = Device is initialised, but pends external configuration and an explicit turning on, + ON = Device is fully configured, functional, controls the hardware, and is possibly actively running, + FAULT = Device detected an unrecoverable error, and is thus malfunctional, + OFF = Device is turned off, drops connection to the hardware, + + The following state transitions are implemented: + boot -> OFF: Triggered by tango. Device will be instantiated, + OFF -> INIT: Triggered by device. Device will initialise (connect to hardware, other devices), + INIT -> STANDBY: Triggered by device. Device is initialised, and is ready for additional configuration by the user, + STANDBY -> ON: Triggered by user. Device reports to be functional, + * -> FAULT: Triggered by device. Device has degraded to malfunctional, for example because the connection to the hardware is lost, + * -> FAULT: Triggered by user. Emulate a forced malfunction for integration testing purposes, + * -> OFF: Triggered by user. Device is turned off. Triggered by the Off() command, + FAULT -> INIT: Triggered by user. Device is reinitialised to recover from an error, + + The user triggers their transitions by the commands reflecting the target state (Initialise(), On(), Fault()). + """ + + # ---------- + # Attributes + # ---------- + + version_R = attribute(dtype=str, access=AttrWriteType.READ, fget=lambda self: get_version()) + + # list of property names too be set first by set_defaults + first_default_settings = [] + + @classmethod + def attr_list(cls): + """ Return a list of all the attribute_wrapper members of this class. """ + return [v for k, v in cls.__dict__.items() if type(v) == attribute_wrapper] + + def setup_value_dict(self): + """ set the initial value for all the attribute wrapper objects""" + + self.value_dict = {i: i.initial_value() for i in self.attr_list()} + + @log_exceptions() + def init_device(self): + """ Instantiates the device in the OFF state. """ + + # NOTE: Will delete_device first, if necessary + Device.init_device(self) + + self.set_state(DevState.OFF) + self.set_status("Device is in the OFF state.") + + # register a proxy to ourselves, to interact with + # our attributes and commands as a client would. + # + # this is required to get/set attributes. + # + # we cannot write directly to our attribute, as that would not + # trigger a write_{name} call. See https://www.tango-controls.org/community/forum/c/development/c/accessing-own-deviceproxy-class/?page=1#post-2021 + self.proxy = DeviceProxy(self.get_name()) + + @log_exceptions() + def delete_device(self): + """Hook to delete resources allocated in init_device. + + This method allows for any memory or other resources allocated in the + init_device method to be released. This method is called by the device + destructor and by the device Init command (a Tango built-in). + """ + logger.info("Shutting down...") + + self.Off() + logger.info("Shut down. Good bye.") + + # -------- + # Commands + # -------- + + @command() + @only_in_states([DevState.OFF]) + @DebugIt() + @fault_on_error() + @log_exceptions() + def Initialise(self): + """ + Command to ask for initialisation of this device. Can only be called in FAULT or OFF state. + + :return:None + """ + self.set_state(DevState.INIT) + self.set_status("Device is in the INIT state.") + + self.setup_value_dict() + + # reload our class & device properties from the Tango database + self.get_device_properties() + + self.configure_for_initialise() + + self.set_state(DevState.STANDBY) + self.set_status("Device is in the STANDBY state.") + + @command() + @only_in_states([DevState.STANDBY, DevState.ON]) + @DebugIt() + @fault_on_error() + @log_exceptions() + def On(self): + """ + Command to ask for initialisation of this device. Can only be called in FAULT or OFF state. + + :return:None + """ + if self.get_state() == DevState.ON: + # Already on. Don't complain. + logger.warning("Requested to go to ON state, but am already in ON state.") + return + + self.configure_for_on() + + self.set_state(DevState.ON) + self.set_status("Device is in the ON state.") + + @command() + @DebugIt() + @log_exceptions() + def Off(self): + """ + Command to ask for shutdown of this device. + + :return:None + """ + if self.get_state() == DevState.OFF: + # Already off. Don't complain. + return + + # Turn off + self.set_state(DevState.OFF) + self.set_status("Device is in the OFF state.") + + self.configure_for_off() + + # Turn off again, in case of race conditions through reconnecting + self.set_state(DevState.OFF) + self.set_status("Device is in the OFF state.") + + + @command() + @only_in_states([DevState.ON, DevState.INIT, DevState.STANDBY, DevState.FAULT]) + @DebugIt() + @log_exceptions() + def Fault(self, new_status="Device is in the FAULT state."): + """ + FAULT state is used to indicate the device broke down, and partially or fully + stopped functioning. Remaining functionality is undefined. + + new_status: The status to set when going to FAULT. + + :return:None + """ + if self.get_state() == DevState.FAULT: + # Already faulting. Don't complain. + logger.warning("Requested to go to FAULT state, but am already in FAULT state.") + return + + self.set_state(DevState.FAULT) + self.set_status(new_status) + + self.configure_for_fault() + + # functions that can or must be overloaded + def configure_for_fault(self): + pass + + @abstractmethod + def configure_for_off(self): + pass + + def configure_for_on(self): + pass + + @abstractmethod + def configure_for_initialise(self): + pass + + def always_executed_hook(self): + """Method always executed before any TANGO command is executed.""" + pass + + @command() + @only_in_states([DevState.STANDBY, DevState.ON]) + @DebugIt() + @log_exceptions() + def set_defaults(self): + """ Set hardware points to their default value. + + A hardware point XXX is set to the value of the object member named XXX_default, if it exists. + XXX_default can be f.e. a constant, or a device_property. + + The points are set in the following order: + 1) The python class property 'first_default_settings' is read, as an array of strings denoting property names. Each property + is set in that order. + 2) Any remaining default properties are set. + """ + + # collect all attributes for which defaults are provided + attributes_with_defaults = [name for name in dir(self) + # collect all attribute members + if isinstance(getattr(self, name), Attribute) + # with a default set + and hasattr(self, f"{name}_default")] + + # determine the order: first do the ones mentioned in default_settings_order + attributes_to_set = self.first_default_settings + [name for name in attributes_with_defaults if name not in self.first_default_settings] + + # set them all + for name in attributes_to_set: + try: + default_value = getattr(self, f"{name}_default") + + # set the attribute to the configured default + logger.debug(f"Setting attribute {name} to {default_value}") + self.proxy.write_attribute(name, default_value) + except Exception as e: + # log which attribute we're addressing + raise Exception(f"Cannot assign default to attribute {name}") from e + + @only_in_states([DevState.STANDBY, DevState.INIT, DevState.ON]) + @fault_on_error() + @command() + def initialise_hardware(self): + """ Initialise the hardware after configuring it. """ + + # This is just the command version of _initialise_hardware(). + self._initialise_hardware() + + def _initialise_hardware(self): + """ Override this method to initialise any hardware after configuring it. """ + pass + + def wait_attribute(self, attr_name, value, timeout=10, pollperiod=0.2): + """ Wait until the given attribute obtains the given value. + + Raises an Exception if it has not after the timeout. + + timeout: time until an Exception is raised, in seconds. + pollperiod: how often to check the attribute, in seconds. + """ + + # Poll every half a second + for _ in range(math.ceil(timeout/pollperiod)): + if getattr(self.proxy, attr_name) != value: + return + + time.sleep(pollperiod) + + raise Exception(f"{attr_name} != {value} after {timeout} seconds still.") diff --git a/devices/devices/observation.py b/tangostationcontrol/tangostationcontrol/devices/observation.py similarity index 77% rename from devices/devices/observation.py rename to tangostationcontrol/tangostationcontrol/devices/observation.py index 0ac9cbc1837fdd8e7ded14bb6c8459226c223866..d43ecdf677d33ff942cdfb18d792b5b1a0609a22 100644 --- a/devices/devices/observation.py +++ b/tangostationcontrol/tangostationcontrol/devices/observation.py @@ -5,25 +5,22 @@ # Distributed under the terms of the APACHE license. # See LICENSE.txt for more info. - -# TODO(Corne): Remove sys.path.append hack once packaging is in place! -import os, sys -currentdir = os.path.dirname(os.path.realpath(__file__)) -parentdir = os.path.dirname(currentdir) -sys.path.append(parentdir) - # PyTango imports from tango import server, Except, DevState, AttrWriteType, DevString, DebugIt from tango.server import Device, run, command, attribute import numpy from time import time -from devices.device_decorators import * -from common.lofar_logging import device_logging_to_python, log_exceptions -from common.lofar_git import get_version +from tangostationcontrol.common.entrypoint import entry +from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions +from tangostationcontrol.common.lofar_version import get_version +from tangostationcontrol.devices.device_decorators import * from json import loads +import logging +logger = logging.getLogger() + __all__ = ["Observation", "main"] @device_logging_to_python() @@ -55,14 +52,14 @@ class Observation(Device): This method is called by the device destructor and by the device Init command (a Tango built-in). """ - self.debug_stream("Shutting down...") + logger.debug("Shutting down...") if self.get_state() != DevState.OFF: self.Off() - self.debug_stream("Shut down. Good bye.") + logger.debug("Shut down. Good bye.") # Lifecycle functions @command(dtype_in = DevString) - @only_in_states([DevState.FAULT, DevState.OFF]) + @only_in_states([DevState.OFF]) @log_exceptions() def Initialise(self, parameters: DevString = None): self.set_state(DevState.INIT) @@ -76,21 +73,21 @@ class Observation(Device): self._observation_id = int(self.observation_parameters.get("id")) self._stop_time = float(self.observation_parameters.get("stop_time")) self.set_state(DevState.STANDBY) - self.info_stream("The observation with ID={} is configured. It will begin as soon as On() is called and it is supposed to stop at {}.".format(self._observation_id, self._stop_time)) + logger.info("The observation with ID={} is configured. It will begin as soon as On() is called and it is supposed to stop at {}.".format(self._observation_id, self._stop_time)) @command() @only_in_states([DevState.STANDBY]) @log_exceptions() def On(self): self.set_state(DevState.ON) - self.info_stream("Started the observation with ID={}.".format(self._observation_id)) + logger.info("Started the observation with ID={}.".format(self._observation_id)) @command() @log_exceptions() def Off(self): self.stop_polling(True) self.set_state(DevState.OFF) - self.info_stream("Stopped the observation with ID={}.".format(self._observation_id)) + logger.info("Stopped the observation with ID={}.".format(self._observation_id)) @only_when_on() @fault_on_error() @@ -117,10 +114,6 @@ class Observation(Device): # ---------- # Run server # ---------- -def main(args = None, **kwargs): +def main(**kwargs): """Main function of the ObservationControl module.""" - return run((Observation,), args = args, **kwargs) - - -if __name__ == '__main__': - main() + return entry(Observation, **kwargs) diff --git a/devices/devices/observation_control.py b/tangostationcontrol/tangostationcontrol/devices/observation_control.py similarity index 85% rename from devices/devices/observation_control.py rename to tangostationcontrol/tangostationcontrol/devices/observation_control.py index 9b60f86bb983057d023483ebaa61164bdfba5bee..2bdae2e850a2b33955fcadc9512e4e6241d0452c 100644 --- a/devices/devices/observation_control.py +++ b/tangostationcontrol/tangostationcontrol/devices/observation_control.py @@ -5,13 +5,6 @@ # Distributed under the terms of the APACHE license. # See LICENSE.txt for more info. - -# TODO(Corne): Remove sys.path.append hack once packaging is in place! -import os, sys -currentdir = os.path.dirname(os.path.realpath(__file__)) -parentdir = os.path.dirname(currentdir) -sys.path.append(parentdir) - # PyTango imports from tango import Except, DevFailed, DevState, AttrWriteType, DebugIt, DeviceProxy, Util, DevBoolean, DevString from tango.server import Device, run, command, device_property, attribute @@ -21,12 +14,14 @@ import numpy import time from json import loads -from devices.device_decorators import * -from common.lofar_logging import device_logging_to_python, log_exceptions -from common.lofar_git import get_version - -from observation import Observation +from tangostationcontrol.common.entrypoint import entry +from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions +from tangostationcontrol.common.lofar_version import get_version +from tangostationcontrol.devices.device_decorators import * +from tangostationcontrol.devices.observation import Observation +import logging +logger = logging.getLogger() __all__ = ["ObservationControl", "main"] @@ -123,7 +118,7 @@ class ObservationControl(Device): # Lifecycle functions @command() - @only_in_states([DevState.FAULT, DevState.OFF]) + @only_in_states([DevState.OFF]) @log_exceptions() @DebugIt() def Initialise(self): @@ -158,7 +153,7 @@ class ObservationControl(Device): @log_exceptions() def read_running_observations_R(self): obs = [ key for key in self.running_observations ] - self.debug_stream("{}".format(obs)) + logger.debug("{}".format(obs)) return obs @log_exceptions() @@ -172,7 +167,7 @@ class ObservationControl(Device): """ if event.err: # Something is fishy with this event. - self.warn_stream("The Observation device {} sent an event but the event signals an error. It is advised to check the logs for any indication that something went wrong in that device. Event data={} ".format(event.device, event)) + logger.warning("The Observation device {} sent an event but the event signals an error. It is advised to check the logs for any indication that something went wrong in that device. Event data={} ".format(event.device, event)) return # Get the Observation ID from the sending device. @@ -182,7 +177,7 @@ class ObservationControl(Device): running_obs = self.running_observations.copy() if not running_obs: # No obs is running??? - self.warn_stream("Received an observation_running event for the observation with ID={}. According to the records in ObservationControl, this observation is not supposed to run. Please check previous logs, especially around the time an observation with this ID was started. Will continue and ignore this event.".format(obs_id)) + logger.warning("Received an observation_running event for the observation with ID={}. According to the records in ObservationControl, this observation is not supposed to run. Please check previous logs, especially around the time an observation with this ID was started. Will continue and ignore this event.".format(obs_id)) return if id in running_obs: @@ -213,7 +208,7 @@ class ObservationControl(Device): # Convert the input parameter to a dict. parameter_dict = loads(parameters) - self.debug_stream("incoming parameter_array = {}, parameter_dict = {}".format(parameters, parameter_dict)) + logger.debug("incoming parameter_array = {}, parameter_dict = {}".format(parameters, parameter_dict)) # Parameter check, do not execute an observation in case # the parameters are not sufficiently defined. @@ -245,10 +240,10 @@ class ObservationControl(Device): except DevFailed as ex: # It is OK if this fails. This likely means that the device did # never exist in the Tango DB. Still add a warning to the logs. - self.warn_stream("Something went wrong when it was attempted to remove the device {} from the Tango DB. You should better go and check the logs. Exception: {}".format(device_name, ex)) + logger.warning("Something went wrong when it was attempted to remove the device {} from the Tango DB. You should better go and check the logs. Exception: {}".format(device_name, ex)) pass else: - self.error_stream("Cannot delete a device from the Tango DB if the device's class name or the device name are not provided: class_name={}, device_name={}".format(class_name, device_name)) + logger.error("Cannot delete a device from the Tango DB if the device's class name or the device name are not provided: class_name={}, device_name={}".format(class_name, device_name)) def create_dynamic_device(self, class_name: str = None, device_name: str = None): """ @@ -261,7 +256,7 @@ class ObservationControl(Device): except DevFailed as ex: self.delete_dynamic_device(class_name, device_name) error_string = "Cannot start the device {} for the device class {}. Exception: {}".format(device_name, class_name, ex) - self.error_stream("{}, {}".format(error_string, ex)) + logger.exception(error_string) Except.re_throw_exception(ex, "DevFailed", error_string, __name__) # API @@ -288,7 +283,7 @@ class ObservationControl(Device): self.create_dynamic_device(class_name, device_name) except DevFailed as ex: error_string = "Cannot create the Observation device instance {} for ID={}. This means that the observation did not start.".format(device_name, obs_id) - self.error_stream(error_string) + logger.exception(error_string) Except.re_throw_exception(ex, "DevFailed", error_string, __name__) try: @@ -308,7 +303,7 @@ class ObservationControl(Device): # Remove the device again. self.delete_dynamic_device(class_name, device_name) error_string = "Cannot access the Observation device instance for observation ID={} with device class name={} and device instance name={}. This means that the observation likely did not start but certainly cannot be controlled and/or forcefully be stopped.".format(obs_id, class_name, device_name) - self.error_stream("{}, {}".format(error_string, ex)) + logger.exception(error_string) Except.re_throw_exception(ex, "DevFailed", error_string, __name__) try: @@ -338,11 +333,11 @@ class ObservationControl(Device): # Finally update the self.running_observation dict's entry of this # observation with the complete set of info. self.running_observations[obs_id] = observation - self.info_stream("Successfully started an observation with ID={}.".format(obs_id)) + logger.info("Successfully started an observation with ID={}.".format(obs_id)) except DevFailed as ex: self.delete_dynamic_device(class_name, device_name) error_string = "Cannot access the Observation device instance for observation ID={} with device class name={} and device instance name={}. This means that the observation cannot be controlled and/or forcefully be stopped.".format(obs_id, Observation.__name__, device_name) - self.error_stream("{}, {}".format(error_string, ex)) + logger.exception(error_string) Except.re_throw_exception(ex, "DevFailed", error_string, __name__) @command(dtype_in = numpy.int64) @@ -359,7 +354,7 @@ class ObservationControl(Device): error = "Cannot stop an observation with ID={}, because the observation is not running.".format(obs_id) Except.throw_exception("IllegalCommand", error, __name__) - self.info_stream("Stopping the observation with ID={}.".format(obs_id)) + logger.info("Stopping the observation with ID={}.".format(obs_id)) # Fetch the obs data and remove it from the dict of # currently running observations. observation = self.running_observations.pop(obs_id) @@ -369,7 +364,7 @@ class ObservationControl(Device): try: device_proxy.ping() except DevFailed: - self.warn_stream("The device for the Observation with ID={} has unexpectedly already disappeared. It is advised to check the logs up to 10s prior to this message to see what happened.".format(obs_id)) + logger.warning("The device for the Observation with ID={} has unexpectedly already disappeared. It is advised to check the logs up to 10s prior to this message to see what happened.".format(obs_id)) else: # Unsubscribe from the subscribed event. event_id = observation.pop("event_id") @@ -394,15 +389,15 @@ class ObservationControl(Device): remaining_wait_time = remaining_wait_time - sleep_time # Check if the observation object is really in OFF state. if stopped: - self.info_stream("Successfully stopped the observation with ID={}.".format(obs_id)) + logger.info("Successfully stopped the observation with ID={}.".format(obs_id)) else: - self.warn_stream("Could not shut down the Observation device (\"{}\") for observation ID={}. This means that there is a chance for a memory leak. Will continue anyway and forcefully delete the Observation object.".format(observation["device_name"], obs_id)) + logger.warning("Could not shut down the Observation device (\"{}\") for observation ID={}. This means that there is a chance for a memory leak. Will continue anyway and forcefully delete the Observation object.".format(observation["device_name"], obs_id)) # Finally remove the device object from the Tango DB. try: self.delete_dynamic_device(observation["class_name"], observation["device_name"]) except DevFailed: - self.warn_stream("Something went wrong when the device {} was removed from the Tango DB. There is nothing that can be done about this here at this moment but you should check the Tango DB yourself.".format(observation["device_name"])) + logger.warning("Something went wrong when the device {} was removed from the Tango DB. There is nothing that can be done about this here at this moment but you should check the Tango DB yourself.".format(observation["device_name"])) @command() @only_when_on() @@ -430,9 +425,9 @@ class ObservationControl(Device): observation = self.running_observations.get(obs_id) info = "An observation with ID={} is".format(obs_id) if observation is not None: - self.debug_stream("{} running.".format(info)) + logger.debug("{} running.".format(info)) return True - self.debug_stream("{} not running.".format(info)) + logger.debug("{} not running.".format(info)) return False @command(dtype_out = DevBoolean) @@ -445,10 +440,6 @@ class ObservationControl(Device): # ---------- # Run server # ---------- -def main(args = None, **kwargs): +def main(**kwargs): """Main function of the ObservationControl module.""" - return run((ObservationControl, Observation), verbose = True, args = args, **kwargs) - - -if __name__ == '__main__': - main() + return entry((ObservationControl, Observation), verbose=True, **kwargs) diff --git a/tangostationcontrol/tangostationcontrol/devices/opcua_device.py b/tangostationcontrol/tangostationcontrol/devices/opcua_device.py new file mode 100644 index 0000000000000000000000000000000000000000..9f846533533e5211cbb7a5aa5018b87364f08463 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/opcua_device.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +# +# This file represents a top-level device +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" Generic OPC-UA Device Server for LOFAR2.0 + +""" + +# TODO(Corne): Remove sys.path.append hack once packaging is in place! +import os, sys +currentdir = os.path.dirname(os.path.realpath(__file__)) +parentdir = os.path.dirname(currentdir) +sys.path.append(parentdir) + +# PyTango imports +from tango import DebugIt +from tango.server import device_property, attribute +from tango import AttrWriteType +import numpy +import asyncio +# Additional import + +from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions +from tangostationcontrol.clients.opcua_client import OPCUAConnection +from tangostationcontrol.devices.device_decorators import * +from tangostationcontrol.devices.lofar_device import lofar_device + +import logging +logger = logging.getLogger() + +__all__ = ["opcua_device", "main"] + +class opcua_device(lofar_device): + """ + + **Properties:** + + - Device Property + OPC_Server_Name + - Type:'DevString' + OPC_Server_Port + - Type:'DevULong' + OPC_Time_Out + - Type:'DevDouble' + """ + + # ----------------- + # Device Properties + # ----------------- + + OPC_Server_Name = device_property( + dtype='DevString', + mandatory=True + ) + + OPC_Server_Port = device_property( + dtype='DevULong', + mandatory=True + ) + + OPC_Time_Out = device_property( + dtype='DevDouble', + mandatory=True + ) + + OPC_namespace = device_property( + dtype='DevString', + mandatory=False, + default_value="http://lofar.eu" + ) + + # Add these elements to the OPC-UA node path. + OPC_Node_Path_Prefix = device_property( + dtype='DevVarStringArray', + mandatory=False, + default_value=[] + ) + + # ---------- + # Attributes + # ---------- + + opcua_missing_attributes_R = attribute(max_dim_x=128, dtype=(str,), fget=lambda self: numpy.array(self.opcua_missing_attributes, dtype=str), doc="OPC-UA attributes that this device requested, but which are not exposed on the server. These attributes are replaced with a no-op and thus do not function as expected.") + + # -------- + # overloaded functions + # -------- + + @log_exceptions() + def configure_for_initialise(self): + """ user code here. is called when the state is set to INIT """ + + # set up the OPC ua client + self.opcua_connection = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), self.OPC_namespace, self.OPC_Time_Out, self.Fault) + self.opcua_connection.node_path_prefix = self.OPC_Node_Path_Prefix + + self.opcua_missing_attributes = [] + + # schedule the opc-ua initialisation, and wait for it to finish + future = asyncio.run_coroutine_threadsafe(self._connect_opcua(), self.opcua_connection.event_loop) + _ = future.result() + + async def _connect_opcua(self): + # connect + await self.opcua_connection.start() + + # map an access helper class + for i in self.attr_list(): + try: + if not i.comms_id or i.comms_id == OPCUAConnection: + await i.async_set_comm_client(self.opcua_connection) + except Exception as e: + # use the pass function instead of setting read/write fails + i.set_pass_func() + self.opcua_missing_attributes.append(",".join(self.opcua_connection.get_node_path(i.comms_annotation))) + + logger.warning("error while setting the attribute {} read/write function. {}".format(i, e)) + + @log_exceptions() + def configure_for_off(self): + """ user code here. is called when the state is set to OFF """ + try: + # disconnect + self.opcua_connection.sync_stop() + except Exception as e: + logger.warning("Exception while stopping OPC ua connection in configure_for_off function: {}. Exception ignored".format(e)) diff --git a/tangostationcontrol/tangostationcontrol/devices/recv.py b/tangostationcontrol/tangostationcontrol/devices/recv.py new file mode 100644 index 0000000000000000000000000000000000000000..4ac93d85abcf66d4c8dc25ab60a1fae89fbef794 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/recv.py @@ -0,0 +1,233 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the RECV project +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" RECV Device Server for LOFAR2.0 + +""" + +# PyTango imports +from tango import DebugIt +from tango.server import run, command +from tango.server import device_property, attribute +from tango import AttrWriteType, DevState +import numpy + +# Additional import +from tangostationcontrol.common.entrypoint import entry +from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions +from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper +from tangostationcontrol.devices.device_decorators import * +from tangostationcontrol.devices.opcua_device import opcua_device + +import logging +logger = logging.getLogger() + +__all__ = ["RECV", "main"] + +@device_logging_to_python() +class RECV(opcua_device): + # ----------------- + # Device Properties + # ----------------- + + ANT_mask_RW_default = device_property( + dtype='DevVarBooleanArray', + mandatory=False, + default_value=[[True] * 3] * 32 + ) + + RCU_mask_RW_default = device_property( + dtype='DevVarBooleanArray', + mandatory=False, + default_value=[True] * 32 + ) + + first_default_settings = [ + # set the masks first, as those filter any subsequent settings + 'ANT_mask_RW', + 'RCU_mask_RW' + ] + + # ---------- + # Attributes + # ---------- + ANT_status_R = attribute(dtype=(str,), max_dim_x=3, max_dim_y=32) + RCU_LED_colour_R = attribute(dtype=(numpy.uint32,), max_dim_x=32, fget=lambda self: (2 * self.proxy.RCU_LED_green_on_R + 4 * self.proxy.RCU_LED_red_on_R).astype(numpy.uint32)) + + ANT_mask_RW = attribute_wrapper(comms_annotation=["ANT_mask_RW" ],datatype=numpy.bool_ , dims=(3,32), access=AttrWriteType.READ_WRITE) + HBAT_BF_delays_R = attribute_wrapper(comms_annotation=["HBAT_BF_delays_R" ],datatype=numpy.int64 , dims=(32,96)) + HBAT_BF_delays_RW = attribute_wrapper(comms_annotation=["HBAT_BF_delays_RW" ],datatype=numpy.int64 , dims=(32,96), access=AttrWriteType.READ_WRITE) + HBAT_LED_on_R = attribute_wrapper(comms_annotation=["HBAT_LED_on_R" ],datatype=numpy.bool_ , dims=(32,96)) + HBAT_LED_on_RW = attribute_wrapper(comms_annotation=["HBAT_LED_on_RW" ],datatype=numpy.bool_ , dims=(32,96), access=AttrWriteType.READ_WRITE) + HBAT_PWR_LNA_on_R = attribute_wrapper(comms_annotation=["HBAT_PWR_LNA_on_R" ],datatype=numpy.bool_ , dims=(32,96)) + HBAT_PWR_LNA_on_RW = attribute_wrapper(comms_annotation=["HBAT_PWR_LNA_on_RW" ],datatype=numpy.bool_ , dims=(32,96), access=AttrWriteType.READ_WRITE) + HBAT_PWR_on_R = attribute_wrapper(comms_annotation=["HBAT_PWR_on_R" ],datatype=numpy.bool_ , dims=(32,96)) + HBAT_PWR_on_RW = attribute_wrapper(comms_annotation=["HBAT_PWR_on_RW" ],datatype=numpy.bool_ , dims=(32,96), access=AttrWriteType.READ_WRITE) + RCU_ADC_locked_R = attribute_wrapper(comms_annotation=["RCU_ADC_locked_R" ],datatype=numpy.bool_ , dims=(3,32)) + RCU_attenuator_dB_R = attribute_wrapper(comms_annotation=["RCU_attenuator_dB_R" ],datatype=numpy.int64 , dims=(3,32)) + RCU_attenuator_dB_RW = attribute_wrapper(comms_annotation=["RCU_attenuator_dB_RW" ],datatype=numpy.int64 , dims=(3,32), access=AttrWriteType.READ_WRITE) + RCU_band_select_R = attribute_wrapper(comms_annotation=["RCU_band_select_R" ],datatype=numpy.int64 , dims=(3,32)) + RCU_band_select_RW = attribute_wrapper(comms_annotation=["RCU_band_select_RW" ],datatype=numpy.int64 , dims=(3,32), access=AttrWriteType.READ_WRITE) + RCU_DTH_freq_R = attribute_wrapper(comms_annotation=["RCU_DTH_freq_R" ],datatype=numpy.int64 , dims=(3,32)) + RCU_DTH_freq_RW = attribute_wrapper(comms_annotation=["RCU_DTH_freq_RW" ],datatype=numpy.int64 , dims=(3,32), access=AttrWriteType.READ_WRITE) + RCU_DTH_on_R = attribute_wrapper(comms_annotation=["RCU_DTH_on_R" ],datatype=numpy.bool_ , dims=(3,32)) + RCU_LED_green_on_R = attribute_wrapper(comms_annotation=["RCU_LED_green_on_R" ],datatype=numpy.bool_ , dims=(32,)) + RCU_LED_green_on_RW = attribute_wrapper(comms_annotation=["RCU_LED_green_on_RW" ],datatype=numpy.bool_ , dims=(32,), access=AttrWriteType.READ_WRITE) + RCU_LED_red_on_R = attribute_wrapper(comms_annotation=["RCU_LED_red_on_R" ],datatype=numpy.bool_ , dims=(32,)) + RCU_LED_red_on_RW = attribute_wrapper(comms_annotation=["RCU_LED_red_on_RW" ],datatype=numpy.bool_ , dims=(32,), access=AttrWriteType.READ_WRITE) + RCU_mask_RW = attribute_wrapper(comms_annotation=["RCU_mask_RW" ],datatype=numpy.bool_ , dims=(32,), access=AttrWriteType.READ_WRITE) + RCU_PCB_ID_R = attribute_wrapper(comms_annotation=["RCU_PCB_ID_R" ],datatype=numpy.int64 , dims=(32,)) + RCU_PCB_number_R = attribute_wrapper(comms_annotation=["RCU_PCB_number_R" ],datatype=numpy.str , dims=(32,)) + RCU_PCB_version_R = attribute_wrapper(comms_annotation=["RCU_PCB_version_R" ],datatype=numpy.str , dims=(32,)) + RCU_PWR_1V8_R = attribute_wrapper(comms_annotation=["RCU_PWR_1V8_R" ],datatype=numpy.float64, dims=(32,)) + RCU_PWR_2V5_R = attribute_wrapper(comms_annotation=["RCU_PWR_2V5_R" ],datatype=numpy.float64, dims=(32,)) + RCU_PWR_3V3_R = attribute_wrapper(comms_annotation=["RCU_PWR_3V3_R" ],datatype=numpy.float64, dims=(32,)) + RCU_PWR_ANALOG_on_R = attribute_wrapper(comms_annotation=["RCU_PWR_ANALOG_on_R" ],datatype=numpy.bool_ , dims=(32,)) + RCU_PWR_ANT_IOUT_R = attribute_wrapper(comms_annotation=["RCU_PWR_ANT_IOUT_R" ],datatype=numpy.float64, dims=(3,32)) + RCU_PWR_ANT_on_R = attribute_wrapper(comms_annotation=["RCU_PWR_ANT_on_R" ],datatype=numpy.bool_ , dims=(3,32)) + RCU_PWR_ANT_on_RW = attribute_wrapper(comms_annotation=["RCU_PWR_ANT_on_RW" ],datatype=numpy.bool_ , dims=(3,32), access=AttrWriteType.READ_WRITE) + RCU_PWR_ANT_VIN_R = attribute_wrapper(comms_annotation=["RCU_PWR_ANT_VIN_R" ],datatype=numpy.float64, dims=(3,32)) + RCU_PWR_ANT_VOUT_R = attribute_wrapper(comms_annotation=["RCU_PWR_ANT_VOUT_R" ],datatype=numpy.float64, dims=(3,32)) + RCU_PWR_DIGITAL_on_R = attribute_wrapper(comms_annotation=["RCU_PWR_DIGITAL_on_R" ],datatype=numpy.bool_ , dims=(32,)) + RCU_PWR_good_R = attribute_wrapper(comms_annotation=["RCU_PWR_good_R" ],datatype=numpy.bool_ , dims=(32,)) + RCU_TEMP_R = attribute_wrapper(comms_annotation=["RCU_TEMP_R" ],datatype=numpy.float64, dims=(32,)) + RECVTR_I2C_error_R = attribute_wrapper(comms_annotation=["RECVTR_I2C_error_R" ],datatype=numpy.int64 , dims=(32,)) + RECVTR_monitor_rate_RW = attribute_wrapper(comms_annotation=["RECVTR_monitor_rate_RW" ],datatype=numpy.int64 , access=AttrWriteType.READ_WRITE) + RECVTR_translator_busy_R = attribute_wrapper(comms_annotation=["RECVTR_translator_busy_R" ],datatype=numpy.bool_ ) + + # -------- + # overloaded functions + # -------- + + # -------- + # Commands + # -------- + @command() + @DebugIt() + @only_when_on() + def RCU_off(self): + """ + + :return:None + """ + self.opcua_connection.call_method(["RCU_off"]) + + @command() + @DebugIt() + @only_when_on() + def RCU_on(self): + """ + + :return:None + """ + self.opcua_connection.call_method(["RCU_on"]) + + @command() + @DebugIt() + @only_when_on() + def RCU_DTH_off(self): + """ + + :return:None + """ + self.opcua_connection.call_method(["RCU_DTH_off"]) + + @command() + @DebugIt() + @only_when_on() + def RCU_DTH_on(self): + """ + + :return:None + """ + self.opcua_connection.call_method(["RCU_DTH_on"]) + + def _initialise_hardware(self): + """ Initialise the RCU hardware. """ + + # method calls don't work yet, so don't use them to allow the boot + # device to initialise us without errors + logger.error("OPC-UA methods not supported yet, not initialising RCU hardware!") + return + + # Cycle RCUs + self.RCU_off() + self.wait_attribute("RECVTR_translator_busy_R", False, 5) + self.RCU_on() + self.wait_attribute("RECVTR_translator_busy_R", False, 5) + + def read_RCU_status_R(self): + """ Returns a set of strings denoting the status of each RCU. + + An empty string means no problems were detected. A non-empty + string means the RCU seems unusable. + + This function can be used as input to modify the RCU_mask_RW. """ + + rcu_mask = self.proxy.RCU_mask_RW + i2c_errors = self.proxy.RCU_I2C_STATUS_R + + nr_rcus = len(rcu_mask) + rcu_status = [""] * nr_rcus + + # construct status of each RCU + for rcu in range(nr_rcus): + status = [] + + if not i2c_status[rcu]: + status.append("[I2C error]") + + rcu_status[rcu] = " ".join(status) + + return rcu_status + + def read_ANT_status_R(self): + """ Returns a set of strings denoting the status of each antenna. + + An empty string means no problems were detected. A non-empty + string means the antenna seems unusable. + + This function can be used as input to modify the Ant_mask_RW. """ + + ant_mask = self.proxy.ANT_mask_RW + rcu_mask = self.proxy.RCU_mask_RW + adc_lock = self.proxy.RCU_ADC_locked_R + i2c_errors = self.proxy.RCU_I2C_STATUS_R + + nr_rcus = len(ant_mask) + nr_ants_per_rcu = len(ant_mask[0]) + + # Collect status, join them into a single string per antenna later + ant_status = [""] * nr_ants + + + for rcu in range(nr_rcus): + for ant in range(nr_ants_per_rcu): + status = [] + + if i2c_status[rcu] != 0: + status.append("[I2C error]") + + if not rcu_mask[rcu]: + status.append("[RCU masked out]") + + if not adc_lock[rcu][ant]: + status.append("[ADC lock error]") + + ant_status[rcu][ant] = " ".join(status) + + return ant_status + + +# ---------- +# Run server +# ---------- +def main(**kwargs): + """Main function of the RECV module.""" + return entry(RECV, **kwargs) diff --git a/devices/test/__init__.py b/tangostationcontrol/tangostationcontrol/devices/sdp/__init__.py similarity index 100% rename from devices/test/__init__.py rename to tangostationcontrol/tangostationcontrol/devices/sdp/__init__.py diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py new file mode 100644 index 0000000000000000000000000000000000000000..4c4b03f8a75d026f2f322ea9b301b9d2b537a6ba --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the SDP project +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" SDP Device Server for LOFAR2.0 + +""" + +# PyTango imports +from tango.server import run +from tango.server import device_property, attribute +from tango import AttrWriteType + +# Additional import +from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper +from tangostationcontrol.devices.opcua_device import opcua_device +from tangostationcontrol.common.entrypoint import entry +from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions + +import numpy + +__all__ = ["SDP", "main"] + + +@device_logging_to_python() +class SDP(opcua_device): + # ----------------- + # Device Properties + # ----------------- + + TR_fpga_mask_RW_default = device_property( + dtype='DevVarBooleanArray', + mandatory=False, + default_value=[True] * 16 + ) + + FPGA_processing_enable_RW_default = device_property( + dtype='DevVarBooleanArray', + mandatory=False, + default_value=[True] * 16 + ) + + FPGA_wg_enable_RW_default = device_property( + dtype='DevVarBooleanArray', + mandatory=False, + default_value=[[False] * 12] * 16 + ) + + # If we enable the waveform generator, we want some sane defaults. + + FPGA_wg_amplitude_RW = device_property( + dtype='DevVarDoubleArray', + mandatory=False, + default_value=[[0.1] * 12] * 16 + ) + + FPGA_wg_frequency_RW = device_property( + dtype='DevVarDoubleArray', + mandatory=False, + # Emit a signal on subband 102 + default_value=[[102 * 200e6/1024] * 12] * 16 + ) + + FPGA_wg_phase_RW = device_property( + dtype='DevVarDoubleArray', + mandatory=False, + default_value=[[0.0] * 12] * 16 + ) + + FPGA_sdp_info_station_id_RW_default = device_property( + dtype='DevVarULongArray', + mandatory=False, + default_value=[0] * 16 + ) + + FPGA_subband_weights_RW_default = device_property( + dtype='DevVarULongArray', + mandatory=False, + default_value=[[8192] * 12 * 512] * 16 + ) + + first_default_settings = [ + # set the masks first, as those filter any subsequent settings + 'TR_fpga_mask_RW' + ] + + # ---------- + # Attributes + # ---------- + + FPGA_beamlet_output_enable_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_enable_R"], datatype=numpy.bool_, dims=(16,)) + FPGA_beamlet_output_enable_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_enable_RW"], datatype=numpy.bool_, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_beamlet_output_hdr_eth_destination_mac_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_eth_destination_mac_R"], datatype=numpy.str, dims=(16,)) + FPGA_beamlet_output_hdr_eth_destination_mac_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_eth_destination_mac_RW"], datatype=numpy.str, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_beamlet_output_hdr_ip_destination_address_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_ip_destination_address_R"], datatype=numpy.str, dims=(16,)) + FPGA_beamlet_output_hdr_ip_destination_address_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_ip_destination_address_RW"], datatype=numpy.str, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_beamlet_output_hdr_udp_destination_port_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_udp_destination_port_R"], datatype=numpy.uint16, dims=(16,)) + FPGA_beamlet_output_hdr_udp_destination_port_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_udp_destination_port_RW"], datatype=numpy.uint16, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_beamlet_output_scale_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_scale_R"], datatype=numpy.uint32, dims=(16,)) + FPGA_beamlet_output_scale_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_scale_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_firmware_version_R = attribute_wrapper(comms_annotation=["FPGA_firmware_version_R"], datatype=numpy.str, dims=(16,)) + FPGA_global_node_index_R = attribute_wrapper(comms_annotation=["FPGA_global_node_index_R"], datatype=numpy.uint32, dims=(16,)) + FPGA_hardware_version_R = attribute_wrapper(comms_annotation=["FPGA_hardware_version_R"], datatype=numpy.str, dims=(16,)) + FPGA_processing_enable_R = attribute_wrapper(comms_annotation=["FPGA_processing_enable_R"], datatype=numpy.bool_, dims=(16,)) + FPGA_processing_enable_RW = attribute_wrapper(comms_annotation=["FPGA_processing_enable_RW"], datatype=numpy.bool_, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_scrap_R = attribute_wrapper(comms_annotation=["FPGA_scrap_R"], datatype=numpy.int32, dims=(8192,)) + FPGA_scrap_RW = attribute_wrapper(comms_annotation=["FPGA_scrap_RW"], datatype=numpy.int32, dims=(8192,), access=AttrWriteType.READ_WRITE) + FPGA_sdp_info_antenna_band_index_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_antenna_band_index_R"], datatype=numpy.uint32, dims=(16,)) + FPGA_sdp_info_block_period_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_block_period_R"], datatype=numpy.uint32, dims=(16,)) + FPGA_sdp_info_f_adc_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_f_adc_R"], datatype=numpy.uint32, dims=(16,)) + FPGA_sdp_info_fsub_type_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_fsub_type_R"], datatype=numpy.uint32, dims=(16,)) + FPGA_sdp_info_nyquist_sampling_zone_index_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_nyquist_sampling_zone_index_R"], datatype=numpy.uint32, dims=(16,)) + FPGA_sdp_info_nyquist_sampling_zone_index_RW = attribute_wrapper(comms_annotation=["FPGA_sdp_info_nyquist_sampling_zone_index_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_sdp_info_observation_id_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_observation_id_R"], datatype=numpy.uint32, dims=(16,)) + FPGA_sdp_info_observation_id_RW = attribute_wrapper(comms_annotation=["FPGA_sdp_info_observation_id_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_sdp_info_station_id_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_station_id_R"], datatype=numpy.uint32, dims=(16,)) + FPGA_sdp_info_station_id_RW = attribute_wrapper(comms_annotation=["FPGA_sdp_info_station_id_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_subband_weights_R = attribute_wrapper(comms_annotation=["FPGA_subband_weights_R"], datatype=numpy.uint32, dims=(12 * 512, 16)) + FPGA_subband_weights_RW = attribute_wrapper(comms_annotation=["FPGA_subband_weights_RW"], datatype=numpy.uint32, dims=(12 * 512, 16), access=AttrWriteType.READ_WRITE) + FPGA_temp_R = attribute_wrapper(comms_annotation=["FPGA_temp_R"], datatype=numpy.float_, dims=(16,)) + FPGA_weights_R = attribute_wrapper(comms_annotation=["FPGA_weights_R"], datatype=numpy.int16, dims=(12 * 488 * 2, 16)) + FPGA_weights_RW = attribute_wrapper(comms_annotation=["FPGA_weights_RW"], datatype=numpy.int16, dims=(12 * 488 * 2, 16), access=AttrWriteType.READ_WRITE) + FPGA_wg_amplitude_R = attribute_wrapper(comms_annotation=["FPGA_wg_amplitude_R"], datatype=numpy.float_, dims=(12, 16)) + FPGA_wg_amplitude_RW = attribute_wrapper(comms_annotation=["FPGA_wg_amplitude_RW"], datatype=numpy.float_, dims=(12, 16), access=AttrWriteType.READ_WRITE) + FPGA_wg_enable_R = attribute_wrapper(comms_annotation=["FPGA_wg_enable_R"], datatype=numpy.bool_, dims=(12, 16)) + FPGA_wg_enable_RW = attribute_wrapper(comms_annotation=["FPGA_wg_enable_RW"], datatype=numpy.bool_, dims=(12, 16), access=AttrWriteType.READ_WRITE) + FPGA_wg_frequency_R = attribute_wrapper(comms_annotation=["FPGA_wg_frequency_R"], datatype=numpy.float_, dims=(12, 16)) + FPGA_wg_frequency_RW = attribute_wrapper(comms_annotation=["FPGA_wg_frequency_RW"], datatype=numpy.float_, dims=(12, 16), access=AttrWriteType.READ_WRITE) + FPGA_wg_phase_R = attribute_wrapper(comms_annotation=["FPGA_wg_phase_R"], datatype=numpy.float_, dims=(12, 16)) + FPGA_wg_phase_RW = attribute_wrapper(comms_annotation=["FPGA_wg_phase_RW"], datatype=numpy.float_, dims=(12, 16), access=AttrWriteType.READ_WRITE) + TR_fpga_mask_R = attribute_wrapper(comms_annotation=["TR_fpga_mask_R"], datatype=numpy.bool_, dims=(16,)) + TR_fpga_mask_RW = attribute_wrapper(comms_annotation=["TR_fpga_mask_RW"], datatype=numpy.bool_, dims=(16,), access=AttrWriteType.READ_WRITE) + TR_fpga_communication_error_R = attribute_wrapper(comms_annotation=["TR_fpga_communication_error_R"], datatype=numpy.bool_, dims=(16,)) + TR_sdp_config_first_fpga_nr_R = attribute_wrapper(comms_annotation=["TR_sdp_config_first_fpga_nr_R"], datatype=numpy.uint32) + TR_sdp_config_nof_beamsets_R = attribute_wrapper(comms_annotation=["TR_sdp_config_nof_beamsets_R"], datatype=numpy.uint32) + TR_sdp_config_nof_fpgas_R = attribute_wrapper(comms_annotation=["TR_sdp_config_nof_fpgas_R"], datatype=numpy.uint32) + TR_software_version_R = attribute_wrapper(comms_annotation=["TR_software_version_R"], datatype=numpy.str) + TR_start_time_R = attribute_wrapper(comms_annotation=["TR_start_time_R"], datatype=numpy.int64) + TR_tod_R = attribute_wrapper(comms_annotation=["TR_tod_R"], datatype=numpy.int64, dims=(2,)) + TR_tod_pps_delta_R = attribute_wrapper(comms_annotation=["TR_tod_pps_delta_R"], datatype=numpy.double) + + S_pn = 12 # Number of ADC signal inputs per Processing Node (PN) FPGA. + N_pn = 16 # Number of FPGAs per antenna band that is controlled via the SC - SDP interface. + + # OPC-UA MP only points for AIT + FPGA_signal_input_mean_R = attribute_wrapper(comms_annotation=["FPGA_signal_input_mean_R"], datatype=numpy.double , dims=(S_pn, N_pn)) + FPGA_signal_input_rms_R = attribute_wrapper(comms_annotation=["FPGA_signal_input_rms_R"], datatype=numpy.double, dims=(S_pn, N_pn)) + + FPGA_jesd204b_csr_rbd_count_R = attribute_wrapper(comms_annotation=["FPGA_jesd204b_csr_rbd_count_R"], datatype=numpy.uint32, dims=(S_pn, N_pn)) + FPGA_jesd204b_csr_dev_syncn_R = attribute_wrapper(comms_annotation=["FPGA_jesd204b_csr_dev_syncn_R"], datatype=numpy.uint32, dims=(S_pn, N_pn)) + FPGA_jesd204b_rx_err0_R = attribute_wrapper(comms_annotation=["FPGA_jesd204b_rx_err0_R"], datatype=numpy.uint32, dims=(S_pn, N_pn)) + FPGA_jesd204b_rx_err1_R = attribute_wrapper(comms_annotation=["FPGA_jesd204b_rx_err1_R"], datatype=numpy.uint32, dims=(S_pn, N_pn)) + + FPGA_bsn_monitor_input_bsn_R = attribute_wrapper(comms_annotation=["FPGA_bsn_monitor_input_bsn_R"], datatype=numpy.int64, dims=(N_pn,)) + FPGA_bsn_monitor_input_nof_packets_R = attribute_wrapper(comms_annotation=["FPGA_bsn_monitor_input_nof_packets_R"], datatype=numpy.int32, dims=(N_pn,)) + FPGA_bsn_monitor_input_nof_valid_R = attribute_wrapper(comms_annotation=["FPGA_bsn_monitor_input_nof_valid_R"], datatype=numpy.int32, dims=(N_pn,)) + FPGA_bsn_monitor_input_nof_err_R = attribute_wrapper(comms_annotation=["FPGA_bsn_monitor_input_nof_err_R"], datatype=numpy.int32, dims=(N_pn,)) + + # -------- + # overloaded functions + # -------- + + # -------- + # Commands + # -------- + +# ---------- +# Run server +# ---------- +def main(**kwargs): + """Main function of the SDP module.""" + return entry(SDP, **kwargs) diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py b/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py new file mode 100644 index 0000000000000000000000000000000000000000..18f000697b5351487ce4c75c0796e2cc81c28740 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the SST project +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" SST Device Server for LOFAR2.0 + +""" + +# PyTango imports +from tango.server import run +from tango.server import device_property, attribute +from tango import AttrWriteType +# Additional import + +from tangostationcontrol.common.entrypoint import entry +from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper +from tangostationcontrol.clients.opcua_client import OPCUAConnection +from tangostationcontrol.clients.statistics_client import StatisticsClient +from tangostationcontrol.devices.sdp.statistics import Statistics +from tangostationcontrol.devices.sdp.statistics_collector import SSTCollector + +import numpy + +__all__ = ["SST", "main"] + + +class SST(Statistics): + + STATISTICS_COLLECTOR_CLASS = SSTCollector + + # ----------------- + # Device Properties + # ----------------- + + FPGA_sst_offload_hdr_eth_destination_mac_RW_default = device_property( + dtype='DevVarStringArray', + mandatory=True + ) + + FPGA_sst_offload_hdr_ip_destination_address_RW_default = device_property( + dtype='DevVarStringArray', + mandatory=True + ) + + FPGA_sst_offload_hdr_udp_destination_port_RW_default = device_property( + dtype='DevVarUShortArray', + mandatory=True + ) + + FPGA_sst_offload_enable_RW_default = device_property( + dtype='DevVarBooleanArray', + mandatory=False, + default_value=[True] * 16 + ) + + FPGA_sst_offload_weighted_subbands_RW_default = device_property( + dtype='DevVarBooleanArray', + mandatory=False, + default_value=[True] * 16 + ) + + first_default_settings = [ + 'FPGA_sst_offload_hdr_eth_destination_mac_RW', + 'FPGA_sst_offload_hdr_ip_destination_address_RW', + 'FPGA_sst_offload_hdr_udp_destination_port_RW', + + 'FPGA_sst_offload_weighted_subbands_RW', + + # enable only after the offloading is configured correctly + 'FPGA_sst_offload_enable_RW' + ] + + # ---------- + # Attributes + # ---------- + + # FPGA control points for SSTs + FPGA_sst_offload_enable_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_sst_offload_enable_RW"], datatype=numpy.bool_, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_sst_offload_enable_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_sst_offload_enable_R"], datatype=numpy.bool_, dims=(16,)) + FPGA_sst_offload_hdr_eth_destination_mac_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_sst_offload_hdr_eth_destination_mac_RW"], datatype=numpy.str, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_sst_offload_hdr_eth_destination_mac_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_sst_offload_hdr_eth_destination_mac_R"], datatype=numpy.str, dims=(16,)) + FPGA_sst_offload_hdr_ip_destination_address_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_sst_offload_hdr_ip_destination_address_RW"], datatype=numpy.str, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_sst_offload_hdr_ip_destination_address_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_sst_offload_hdr_ip_destination_address_R"], datatype=numpy.str, dims=(16,)) + FPGA_sst_offload_hdr_udp_destination_port_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_sst_offload_hdr_udp_destination_port_RW"], datatype=numpy.uint16, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_sst_offload_hdr_udp_destination_port_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_sst_offload_hdr_udp_destination_port_R"], datatype=numpy.uint16, dims=(16,)) + FPGA_sst_offload_weighted_subbands_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_sst_offload_weighted_subbands_RW"], datatype=numpy.bool_, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_sst_offload_weighted_subbands_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_sst_offload_weighted_subbands_R"], datatype=numpy.bool_, dims=(16,)) + + # number of packets with valid payloads + nof_valid_payloads_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "nof_valid_payloads"}, dims=(SSTCollector.MAX_FPGAS,), datatype=numpy.uint64) + # number of packets with invalid payloads + nof_payload_errors_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "nof_payload_errors"}, dims=(SSTCollector.MAX_FPGAS,), datatype=numpy.uint64) + # latest SSTs + sst_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "sst_values"}, dims=(SSTCollector.MAX_SUBBANDS, SSTCollector.MAX_INPUTS), datatype=numpy.uint64) + # reported timestamp for each row in the latest SSTs + sst_timestamp_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "sst_timestamps"}, dims=(SSTCollector.MAX_INPUTS,), datatype=numpy.uint64) + # integration interval for each row in the latest SSTs + integration_interval_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "integration_intervals"}, dims=(SSTCollector.MAX_INPUTS,), datatype=numpy.float32) + # whether the subband data was calibrated by the SDP (that is, were subband weights applied) + subbands_calibrated_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "subbands_calibrated"}, dims=(SSTCollector.MAX_INPUTS,), datatype=numpy.bool_) + + # -------- + # Overloaded functions + # -------- + + # -------- + # Commands + # -------- + +# ---------- +# Run server +# ---------- +def main(**kwargs): + """Main function of the SST Device module.""" + return entry(SST, **kwargs) diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py new file mode 100644 index 0000000000000000000000000000000000000000..4b03f2db7848cf37d1b2d8ccacfcf0ac81e928cc --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the SST project +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" Base device for Statistics (SST/BST/XST) + +""" + +from abc import ABCMeta, abstractmethod + +# PyTango imports +from tango.server import device_property, attribute +from tango import AttrWriteType + +# Additional import +import asyncio + +from tangostationcontrol.clients.statistics_client import StatisticsClient +from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper +from tangostationcontrol.devices.opcua_device import opcua_device +from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions + +import logging + +logger = logging.getLogger() + +import numpy + +__all__ = ["Statistics"] + +class Statistics(opcua_device, metaclass=ABCMeta): + + # In derived classes, set this to a subclass of StatisticsCollector + @property + @abstractmethod + def STATISTICS_COLLECTOR_CLASS(self): + pass + + # ----------------- + # Device Properties + # ----------------- + + Statistics_Client_UDP_Port = device_property( + dtype='DevUShort', + mandatory=True + ) + + Statistics_Client_TCP_Port = device_property( + dtype='DevUShort', + mandatory=True + ) + + # ---------- + # Attributes + # ---------- + + # number of UDP packets and bytes that were received + nof_packets_received_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "udp", "parameter": "nof_packets_received"}, datatype=numpy.uint64) + nof_bytes_received_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "udp", "parameter": "nof_bytes_received"}, datatype=numpy.uint64) + # number of UDP packets that were dropped because we couldn't keep up with processing + nof_packets_dropped_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "udp", "parameter": "nof_packets_dropped"}, datatype=numpy.uint64) + # last packet we processed + last_packet_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "udp", "parameter": "last_packet"}, dims=(9000,), datatype=numpy.uint8) + # when last packet was received + last_packet_timestamp_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "udp", "parameter": "last_packet_timestamp"}, datatype=numpy.uint64) + + # queue fill percentage, as reported by the consumer + queue_collector_fill_percentage_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "queue", "parameter": "collector_fill_percentage"}, datatype=numpy.uint64) + queue_replicator_fill_percentage_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "queue", "parameter": "replicator_fill_percentage"}, datatype=numpy.uint64) + + replicator_clients_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "replicator", "parameter": "clients"}, dims=(128,), datatype=numpy.str) + replicator_nof_bytes_sent_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "replicator", "parameter": "nof_bytes_sent"}, datatype=numpy.uint64) + + replicator_nof_packets_sent_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "replicator", "parameter": "nof_packets_sent"}, datatype=numpy.uint64) + replicator_nof_tasks_pending_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "replicator", "parameter": "nof_tasks_pending"}, datatype=numpy.uint64) + + # number of UDP packets that were processed + nof_packets_processed_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "nof_packets"}, datatype=numpy.uint64) + # number of invalid (non-SST) packets received + nof_invalid_packets_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "nof_invalid_packets"}, datatype=numpy.uint64) + # last packet that could not be parsed + last_invalid_packet_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "last_invalid_packet"}, dims=(9000,), datatype=numpy.uint8) + + # -------- + # Overloaded functions + # -------- + + def configure_for_off(self): + """ user code here. is called when the state is set to OFF """ + + try: + self.statistics_client.sync_stop() + except Exception as e: + logger.exception("Exception while stopping statistics_client in configure_for_off. Exception ignored") + + super().configure_for_off() + + @log_exceptions() + def configure_for_initialise(self): + """ user code here. is called when the sate is set to INIT """ + """Initialises the attributes and properties of the statistics device.""" + + super().configure_for_initialise() + + # Options for UDPReceiver + udp_options = { + "udp_port": self.Statistics_Client_UDP_Port, + "udp_host": "0.0.0.0" + } + + # Options for TCPReplicator + tcp_options = { + "tcp_port": self.Statistics_Client_TCP_Port + # tcp_host has default value + } + + self.statistics_collector = self.STATISTICS_COLLECTOR_CLASS() + self.statistics_client = StatisticsClient(self.statistics_collector, udp_options, tcp_options, self.Fault, self.opcua_connection.event_loop) # can share event loop + + # schedule the opc-ua initialisation, and wait for it to finish + future = asyncio.run_coroutine_threadsafe(self._connect_statistics(), self.statistics_client.event_loop) + _ = future.result() + + async def _connect_statistics(self): + # map an access helper class + for i in self.attr_list(): + try: + if i.comms_id == StatisticsClient: + await i.async_set_comm_client(self.statistics_client) + except Exception as e: + # use the pass function instead of setting read/write fails + i.set_pass_func() + logger.warning("error while setting the sst attribute {} read/write function. {}. using pass function instead".format(i, e)) + + await self.statistics_client.start() + + # -------- + # Commands + # -------- diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/statistics_collector.py b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics_collector.py new file mode 100644 index 0000000000000000000000000000000000000000..29503ca58ef25fcd3cb0b4ced4ff09ddaa62ecc6 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics_collector.py @@ -0,0 +1,281 @@ +from queue import Queue +from threading import Thread +import logging +import numpy + +from .statistics_packet import SSTPacket, XSTPacket +from tangostationcontrol.common.baselines import nr_baselines, baseline_index, baseline_from_index +from tangostationcontrol.clients.statistics_client_thread import StatisticsClientThread + +logger = logging.getLogger() + +class StatisticsCollector: + """ Base class to process statistics packets into parameters matrices. """ + + # Maximum number of FPGAs we receive data from (used for diagnostics) + MAX_FPGAS = 16 + + def __init__(self): + self.parameters = self._default_parameters() + + def _default_parameters(self): + return { + "nof_packets": numpy.uint64(0), + + # Packet count for packets that could not be parsed + "nof_invalid_packets": numpy.uint64(0), + + # Full contents of the latest packet we deemed invalid. + "last_invalid_packet": numpy.zeros((9000,), dtype=numpy.uint8), + } + + def process_packet(self, packet): + self.parameters["nof_packets"] += numpy.uint64(1) + + try: + self.parse_packet(packet) + except Exception as e: + self.parameters["last_invalid_packet"] = numpy.frombuffer(packet, dtype=numpy.uint8) + self.parameters["nof_invalid_packets"] += numpy.uint64(1) + + raise ValueError("Could not parse statistics packet") from e + + def parse_packet(self, packet): + """ Update any information based on this packet. """ + + raise NotImplementedError + + +class SSTCollector(StatisticsCollector): + """ Class to process SST statistics packets. """ + + # Maximum number of antenna inputs we support (used to determine array sizes) + MAX_INPUTS = 192 + + # Maximum number of subbands we support (used to determine array sizes) + MAX_SUBBANDS = 512 + + def _default_parameters(self): + defaults = super()._default_parameters() + + defaults.update({ + # Number of packets received so far that we could parse correctly and do not have a payload error + "nof_valid_payloads": numpy.zeros((self.MAX_FPGAS,), dtype=numpy.uint64), + + # Packets that reported a payload error + "nof_payload_errors": numpy.zeros((self.MAX_FPGAS,), dtype=numpy.uint64), + + # Last value array we've constructed out of the packets + "sst_values": numpy.zeros((self.MAX_INPUTS, self.MAX_SUBBANDS), dtype=numpy.uint64), + "sst_timestamps": numpy.zeros((self.MAX_INPUTS,), dtype=numpy.float64), + "integration_intervals": numpy.zeros((self.MAX_INPUTS,), dtype=numpy.float32), + "subbands_calibrated": numpy.zeros((self.MAX_INPUTS,), dtype=numpy.bool_), + }) + + return defaults + + def parse_packet(self, packet): + fields = SSTPacket(packet) + + # determine which input this packet contains data for + if fields.signal_input_index >= self.MAX_INPUTS: + # packet describes an input that is out of bounds for us + raise ValueError("Packet describes input %d, but we are limited to describing MAX_INPUTS=%d" % (fields.signal_input_index, self.MAX_INPUTS)) + + input_index = fields.signal_input_index + + if fields.payload_error: + # cannot trust the data if a payload error is reported + self.parameters["nof_payload_errors"][fields.gn_index] += numpy.uint64(1) + + # don't raise, as packet is valid + return + + # process the packet + self.parameters["nof_valid_payloads"][fields.gn_index] += numpy.uint64(1) + self.parameters["sst_values"][input_index][:fields.nof_statistics_per_packet] = fields.payload + self.parameters["sst_timestamps"][input_index] = numpy.float64(fields.timestamp().timestamp()) + self.parameters["integration_intervals"][input_index] = fields.integration_interval() + self.parameters["subbands_calibrated"][input_index] = fields.subband_calibrated_flag + +class XSTCollector(StatisticsCollector): + """ Class to process XST statistics packets. """ + + # Maximum number of antenna inputs we support (used to determine array sizes) + MAX_INPUTS = 192 + + # Maximum number of baselines we can receive + MAX_BASELINES = nr_baselines(MAX_INPUTS) + + # Expected block size is BLOCK_LENGTH x BLOCK_LENGTH + BLOCK_LENGTH = 12 + + # Expected number of blocks: enough to cover all baselines without the conjugates (that is, the top-left triangle of the matrix). + MAX_BLOCKS = nr_baselines(MAX_INPUTS // BLOCK_LENGTH) + + # Maximum number of subbands we support (used to determine array sizes) + MAX_SUBBANDS = 512 + + # Complex values are (real, imag). A bit silly, but we don't want magical constants. + VALUES_PER_COMPLEX = 2 + + def _default_parameters(self): + defaults = super()._default_parameters() + + defaults.update({ + # Number of packets received so far that we could parse correctly and do not have a payload error + "nof_valid_payloads": numpy.zeros((self.MAX_FPGAS,), dtype=numpy.uint64), + + # Packets that reported a payload error + "nof_payload_errors": numpy.zeros((self.MAX_FPGAS,), dtype=numpy.uint64), + + # Last value array we've constructed out of the packets + "xst_blocks": numpy.zeros((self.MAX_BLOCKS, self.BLOCK_LENGTH * self.BLOCK_LENGTH * self.VALUES_PER_COMPLEX), dtype=numpy.int64), + # Whether the values are actually conjugated and transposed + "xst_conjugated": numpy.zeros((self.MAX_BLOCKS,), dtype=numpy.bool_), + "xst_timestamps": numpy.zeros((self.MAX_BLOCKS,), dtype=numpy.float64), + "xst_subbands": numpy.zeros((self.MAX_BLOCKS,), dtype=numpy.uint16), + "integration_intervals": numpy.zeros((self.MAX_BLOCKS,), dtype=numpy.float32), + }) + + return defaults + + def parse_packet(self, packet): + fields = XSTPacket(packet) + + if fields.payload_error: + # cannot trust the data if a payload error is reported + self.parameters["nof_payload_errors"][fields.gn_index] += numpy.uint64(1) + + # don't raise, as packet is valid + return + + # the blocks must be of size BLOCK_LENGTH x BLOCK_LENGTH + if fields.nof_signal_inputs != self.BLOCK_LENGTH: + raise ValueError("Packet describes a block of {0} x {0} baselines, but we can only parse blocks of {1} x {1} baselines".format(fields.nof_signal_inputs, self.BLOCK_LENGTH)) + + # check whether set of baselines in this packet are not out of bounds + for antenna in (0,1): + if fields.first_baseline[antenna] + fields.nof_signal_inputs >= self.MAX_INPUTS: + # packet describes an input that is out of bounds for us + raise ValueError("Packet describes {0} x {0} baselines starting at {1}, but we are limited to describing MAX_INPUTS={2}".format(fields.nof_signal_inputs, fields.first_baseline, self.MAX_INPUTS)) + + # the blocks of baselines need to be tightly packed, and thus be provided at exact intervals + if fields.first_baseline[antenna] % self.BLOCK_LENGTH != 0: + raise ValueError("Packet describes baselines starting at %s, but we require a multiple of BLOCK_LENGTH=%d" % (fields.first_baseline, self.MAX_INPUTS)) + + # Make sure we always have a baseline (a,b) with a>=b. If not, we swap the indices and mark that the data must be conjugated and transposed when processed. + first_baseline = fields.first_baseline + if first_baseline[0] < first_baseline[1]: + conjugated = True + first_baseline = (first_baseline[1], first_baseline[0]) + else: + conjugated = False + + # the payload contains complex values for the block of baselines of size BLOCK_LENGTH x BLOCK_LENGTH + # starting at baseline first_baseline. + # + # we honour this format, as we want to keep the metadata together with these blocks. we do need to put the blocks in a linear + # and tight order, however, so we calculate a block index. + block_index = baseline_index(first_baseline[0] // self.BLOCK_LENGTH, first_baseline[1] // self.BLOCK_LENGTH) + + # We did enough checks on first_baseline for this to be a logic error in our code + assert 0 <= block_index < self.MAX_BLOCKS, f"Received block {block_index}, but have only room for {self.MAX_BLOCKS}. Block starts at baseline {first_baseline}." + + # process the packet + self.parameters["nof_valid_payloads"][fields.gn_index] += numpy.uint64(1) + + self.parameters["xst_blocks"][block_index][:fields.nof_statistics_per_packet] = fields.payload + self.parameters["xst_timestamps"][block_index] = numpy.float64(fields.timestamp().timestamp()) + self.parameters["xst_conjugated"][block_index] = conjugated + self.parameters["xst_subbands"][block_index] = numpy.uint16(fields.subband_index) + self.parameters["integration_intervals"][block_index] = fields.integration_interval() + + def xst_values(self): + """ xst_blocks, but as a matrix[MAX_INPUTS][MAX_INPUTS] of complex values. """ + + matrix = numpy.zeros((self.MAX_INPUTS, self.MAX_INPUTS), dtype=numpy.complex64) + xst_blocks = self.parameters["xst_blocks"] + xst_conjugated = self.parameters["xst_conjugated"] + + for block_index in range(self.MAX_BLOCKS): + # convert real/imag int to complex float values. this works as real/imag come in pairs + block = xst_blocks[block_index].astype(numpy.float32).view(numpy.complex64) + + if xst_conjugated[block_index]: + # block is conjugated and transposed. process. + block = block.conjugate().transpose() + + # reshape into [a][b] + block = block.reshape(self.BLOCK_LENGTH, self.BLOCK_LENGTH) + + # compute destination in matrix + first_baseline = baseline_from_index(block_index) + first_baseline = (first_baseline[0] * self.BLOCK_LENGTH, first_baseline[1] * self.BLOCK_LENGTH) + + # copy block into matrix + matrix[first_baseline[0]:first_baseline[0]+self.BLOCK_LENGTH, first_baseline[1]:first_baseline[1]+self.BLOCK_LENGTH] = block + + return matrix + + +class StatisticsConsumer(Thread, StatisticsClientThread): + """ Base class to process statistics packets from a queue, asynchronously. """ + + # Maximum time to wait for the Thread to get unstuck, if we want to stop + DISCONNECT_TIMEOUT = 10.0 + + # No default options required, for now? + _default_options = {} + + def __init__(self, queue: Queue, collector: StatisticsCollector): + self.queue = queue + self.collector = collector + self.last_packet = None + + super().__init__() + self.start() + + @property + def _options(self) -> dict: + return StatisticsConsumer._default_options + + def run(self): + logger.info("Starting statistics thread") + + while True: + self.last_packet = self.queue.get() + + # This is the exception/slow path, but python doesn't allow us to optimise that + if self.last_packet is None: + # None is the magic marker to stop processing + break + + try: + self.collector.process_packet(self.last_packet) + except ValueError as e: + logger.exception("Could not parse statistics packet") + + # continue processing + + logger.info("Stopped statistics thread") + + def join(self, timeout=0): + # insert magic marker + self.queue.put(None) + logger.info("Sent shutdown to statistics thread") + + super().join(timeout) + + def disconnect(self): + # TODO(Corne): Prevent duplicate code across TCPReplicator, UDPReceiver + # and StatisticsConsumer. + if not self.is_alive(): + return + + # try to get the thread shutdown, but don't stall forever + self.join(self.DISCONNECT_TIMEOUT) + + if self.is_alive(): + # there is nothing we can do except wait (stall) longer, which could be indefinitely. + logger.error(f"Statistics thread did not shut down after {self.DISCONNECT_TIMEOUT} seconds, just leaving it dangling. Please attach a debugger to thread ID {self.ident}.") diff --git a/devices/devices/sdp/statistics_packet.py b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics_packet.py similarity index 92% rename from devices/devices/sdp/statistics_packet.py rename to tangostationcontrol/tangostationcontrol/devices/sdp/statistics_packet.py index 6843c99e62c79b2c9afa119aaf0b3b51709269f7..c98ae9b5bdc604e8a55480cc5473e658b10cefa1 100644 --- a/devices/devices/sdp/statistics_packet.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics_packet.py @@ -1,6 +1,5 @@ import struct from datetime import datetime, timezone -from typing import Tuple import numpy __all__ = ["StatisticsPacket", "SSTPacket", "XSTPacket", "BSTPacket"] @@ -117,9 +116,9 @@ class StatisticsPacket(object): self.nyquist_zone_index = get_bit_value(self.source_info, 13, 14) self.t_adc = get_bit_value(self.source_info, 12) self.fsub_type = get_bit_value(self.source_info, 11) - self.payload_error = get_bit_value(self.source_info, 10) - self.beam_repositioning_flag = get_bit_value(self.source_info, 9) - self.subband_calibrated_flag = get_bit_value(self.source_info, 8) + self.payload_error = (get_bit_value(self.source_info, 10) != 0) + self.beam_repositioning_flag = (get_bit_value(self.source_info, 9) != 0) + self.subband_calibrated_flag = (get_bit_value(self.source_info, 8) != 0) # self.source_info 5-7 are reserved self.gn_index = get_bit_value(self.source_info, 0, 4) @@ -210,6 +209,17 @@ class StatisticsPacket(object): return header + def payload(self, signed=False) -> numpy.array: + """ The payload of this packet, as a linear array. """ + + # derive which and how many elements to read from the packet header + bytecount_to_unsigned_struct_type = {1: 'b', 2: 'h', 4: 'i', 8: 'q'} if signed else {1: 'B', 2: 'H', 4: 'I', 8: 'Q'} + format_str = ">{}{}".format(self.nof_statistics_per_packet, + bytecount_to_unsigned_struct_type[self.nof_bytes_per_statistic]) + + return numpy.array( + struct.unpack(format_str, self.packet[self.header_size:self.header_size + struct.calcsize(format_str)])) + class SSTPacket(StatisticsPacket): """ @@ -245,16 +255,8 @@ class SSTPacket(StatisticsPacket): return header @property - def payload(self) -> numpy.array: - """ The payload of this packet, interpreted as SST data. """ - - # derive which and how many elements to read from the packet header - bytecount_to_unsigned_struct_type = {1: 'B', 2: 'H', 4: 'I', 8: 'Q'} - format_str = ">{}{}".format(self.nof_statistics_per_packet, - bytecount_to_unsigned_struct_type[self.nof_bytes_per_statistic]) - - return numpy.array( - struct.unpack(format_str, self.packet[self.header_size:self.header_size + struct.calcsize(format_str)])) + def payload(self): + return super().payload(signed=False) class XSTPacket(StatisticsPacket): @@ -263,9 +265,10 @@ class XSTPacket(StatisticsPacket): The following fields are exposed as properties & functions. - subband_index: subband number for which this packet contains statistics. - baseline: antenna pair for which this packet contains statistics. + first_baseline: first antenna pair for which this packet contains statistics. + + payload[nof_signal_inputs][nof_signal_inputs] the baselines, starting from first_baseline """ def __init__(self, packet): @@ -281,16 +284,20 @@ class XSTPacket(StatisticsPacket): super().unpack_data_id() self.subband_index = get_bit_value(self.data_id, 16, 24) - self.baseline = (get_bit_value(self.data_id, 8, 15), get_bit_value(self.data_id, 0, 7)) + self.first_baseline = (get_bit_value(self.data_id, 8, 15), get_bit_value(self.data_id, 0, 7)) def header(self): header = super().header() header["data_id"]["subband_index"] = self.subband_index - header["data_id"]["baseline"] = self.baseline + header["data_id"]["first_baseline"] = self.first_baseline return header + @property + def payload(self): + return super().payload(signed=True) + class BSTPacket(StatisticsPacket): """ @@ -323,7 +330,7 @@ class BSTPacket(StatisticsPacket): return header -if __name__ == "__main__": +def main(args=None, **kwargs): # parse one packet from stdin import sys import pprint diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py b/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py new file mode 100644 index 0000000000000000000000000000000000000000..dcbda73c6570f6db966eaf5518e2129ecea41187 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the XST project +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" XST Device Server for LOFAR2.0 + +""" + +# PyTango imports +from tango.server import run +from tango.server import device_property, attribute +from tango import AttrWriteType + +# Additional import +from tangostationcontrol.common.entrypoint import entry +from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper +from tangostationcontrol.clients.opcua_client import OPCUAConnection +from tangostationcontrol.clients.statistics_client import StatisticsClient + +from tangostationcontrol.devices.sdp.statistics import Statistics +from tangostationcontrol.devices.sdp.statistics_collector import XSTCollector + +import numpy + +__all__ = ["XST", "main"] + +class XST(Statistics): + + STATISTICS_COLLECTOR_CLASS = XSTCollector + + # ----------------- + # Device Properties + # ----------------- + + FPGA_xst_offload_hdr_eth_destination_mac_RW_default = device_property( + dtype='DevVarStringArray', + mandatory=True + ) + + FPGA_xst_offload_hdr_ip_destination_address_RW_default = device_property( + dtype='DevVarStringArray', + mandatory=True + ) + + FPGA_xst_offload_hdr_udp_destination_port_RW_default = device_property( + dtype='DevVarUShortArray', + mandatory=True + ) + + FPGA_xst_processing_enable_RW_default = device_property( + dtype='DevVarBooleanArray', + mandatory=False, + default_value=[True] * 16 + ) + + FPGA_xst_subband_select_RW_default = device_property( + dtype='DevVarULongArray', + mandatory=False, + default_value=[[0,102,0,0,0,0,0,0]] * 16 + ) + + FPGA_xst_integration_interval_RW_default = device_property( + dtype='DevVarDoubleArray', + mandatory=False, + default_value=[1.0] * 16 + ) + + FPGA_xst_offload_enable_RW_default = device_property( + dtype='DevVarBooleanArray', + mandatory=False, + default_value=[True] * 16 + ) + + first_default_settings = [ + 'FPGA_xst_offload_hdr_eth_destination_mac_RW', + 'FPGA_xst_offload_hdr_ip_destination_address_RW', + 'FPGA_xst_offload_hdr_udp_destination_port_RW', + + 'FPGA_xst_subband_select_RW', + 'FPGA_xst_integration_interval_RW', + + # enable only after the offloading is configured correctly + 'FPGA_xst_offload_enable_RW' + ] + + # ---------- + # Attributes + # ---------- + + # FPGA control points for XSTs + FPGA_xst_integration_interval_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_integration_interval_RW"], datatype=numpy.double, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_xst_integration_interval_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_integration_interval_R"], datatype=numpy.double, dims=(16,)) + FPGA_xst_offload_enable_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_offload_enable_RW"], datatype=numpy.bool_, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_xst_offload_enable_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_offload_enable_R"], datatype=numpy.bool_, dims=(16,)) + FPGA_xst_offload_hdr_eth_destination_mac_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_offload_hdr_eth_destination_mac_RW"], datatype=numpy.str, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_xst_offload_hdr_eth_destination_mac_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_offload_hdr_eth_destination_mac_R"], datatype=numpy.str, dims=(16,)) + FPGA_xst_offload_hdr_ip_destination_address_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_offload_hdr_ip_destination_address_RW"], datatype=numpy.str, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_xst_offload_hdr_ip_destination_address_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_offload_hdr_ip_destination_address_R"], datatype=numpy.str, dims=(16,)) + FPGA_xst_offload_hdr_udp_destination_port_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_offload_hdr_udp_destination_port_RW"], datatype=numpy.uint16, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_xst_offload_hdr_udp_destination_port_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_offload_hdr_udp_destination_port_R"], datatype=numpy.uint16, dims=(16,)) + FPGA_xst_processing_enable_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_processing_enable_RW"], datatype=numpy.bool_, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_xst_processing_enable_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_processing_enable_R"], datatype=numpy.bool_, dims=(16,)) + FPGA_xst_subband_select_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_subband_select_RW"], datatype=numpy.uint32, dims=(8,16), access=AttrWriteType.READ_WRITE) + FPGA_xst_subband_select_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_subband_select_R"], datatype=numpy.uint32, dims=(8,16)) + + # number of packets with valid payloads + nof_valid_payloads_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "nof_valid_payloads"}, dims=(XSTCollector.MAX_FPGAS,), datatype=numpy.uint64) + # number of packets with invalid payloads + nof_payload_errors_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "nof_payload_errors"}, dims=(XSTCollector.MAX_FPGAS,), datatype=numpy.uint64) + # latest XSTs + xst_blocks_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "xst_blocks"}, dims=(XSTCollector.BLOCK_LENGTH * XSTCollector.BLOCK_LENGTH * XSTCollector.VALUES_PER_COMPLEX, XSTCollector.MAX_BLOCKS), datatype=numpy.int64) + # whether the values in the block are conjugated and transposed + xst_conjugated_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "xst_conjugated"}, dims=(XSTCollector.MAX_BLOCKS,), datatype=numpy.bool_) + # reported timestamp for each row in the latest XSTs + xst_timestamp_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "xst_timestamps"}, dims=(XSTCollector.MAX_BLOCKS,), datatype=numpy.uint64) + # which subband the XSTs describe + xst_subbands_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "xst_subbands"}, dims=(XSTCollector.MAX_BLOCKS,), datatype=numpy.uint16) + # integration interval for each row in the latest XSTs + integration_interval_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "integration_intervals"}, dims=(XSTCollector.MAX_BLOCKS,), datatype=numpy.float32) + + # xst_R, but as a matrix of input x input + xst_real_R = attribute(max_dim_x=XSTCollector.MAX_INPUTS, max_dim_y=XSTCollector.MAX_INPUTS, dtype=((numpy.float32,),)) + xst_imag_R = attribute(max_dim_x=XSTCollector.MAX_INPUTS, max_dim_y=XSTCollector.MAX_INPUTS, dtype=((numpy.float32,),)) + xst_power_R = attribute(max_dim_x=XSTCollector.MAX_INPUTS, max_dim_y=XSTCollector.MAX_INPUTS, dtype=((numpy.float32,),)) + xst_phase_R = attribute(max_dim_x=XSTCollector.MAX_INPUTS, max_dim_y=XSTCollector.MAX_INPUTS, dtype=((numpy.float32,),)) + + def read_xst_real_R(self): + return numpy.real(self.statistics_client.collector.xst_values()) + + def read_xst_imag_R(self): + return numpy.imag(self.statistics_client.collector.xst_values()) + + def read_xst_power_R(self): + return numpy.abs(self.statistics_client.collector.xst_values()) + + def read_xst_phase_R(self): + return numpy.angle(self.statistics_client.collector.xst_values()) + + # -------- + # Overloaded functions + # -------- + + # -------- + # Commands + # -------- + +# ---------- +# Run server +# ---------- +def main(**kwargs): + """Main function of the XST Device module.""" + return entry(XST, **kwargs) diff --git a/tangostationcontrol/tangostationcontrol/devices/unb2.py b/tangostationcontrol/tangostationcontrol/devices/unb2.py new file mode 100644 index 0000000000000000000000000000000000000000..c4f623c5df5846858dd8d0aa0ab5fd53dff56ac3 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/unb2.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the SDP project +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" SDP Device Server for LOFAR2.0 + +""" + +# PyTango imports +from tango.server import run, command +from tango.server import device_property, attribute +from tango import AttrWriteType, DebugIt, DevState +# Additional import + +from tangostationcontrol.common.entrypoint import entry +from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper +from tangostationcontrol.devices.opcua_device import opcua_device +from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions +from tangostationcontrol.devices.device_decorators import only_when_on + +import numpy + +__all__ = ["UNB2", "main"] + +@device_logging_to_python() +class UNB2(opcua_device): + # ----------------- + # Device Properties + # ----------------- + + UNB2_mask_RW_default = device_property( + dtype='DevVarBooleanArray', + mandatory=False, + default_value=[True] * 2 + ) + + # ---------- + # Attributes + # ---------- + + N_unb = 2 + N_fpga = 4 + N_ddr = 2 + N_qsfp = 6 + + UNB2_mask_RW_default = device_property( + dtype='DevVarBooleanArray', + mandatory=False, + default_value=[True] * 2 + ) + + first_default_settings = [ + # set the masks first, as those filter any subsequent settings + 'UNB2_mask_RW' + ] + + UNB2TR_I2C_bus_DDR4_error_R = attribute_wrapper(comms_annotation=["UNB2TR_I2C_bus_DDR4_error_R"],datatype=numpy.int64 , dims=(4,2)) + UNB2TR_I2C_bus_error_R = attribute_wrapper(comms_annotation=["UNB2TR_I2C_bus_error_R" ],datatype=numpy.int64 , dims=(2,)) + UNB2TR_I2C_bus_FPGA_PS_error_R = attribute_wrapper(comms_annotation=["UNB2TR_I2C_bus_FPGA_PS_error_R"],datatype=numpy.int64 , dims=(4,2)) + UNB2TR_I2C_bus_PS_error_R = attribute_wrapper(comms_annotation=["UNB2TR_I2C_bus_PS_error_R" ],datatype=numpy.int64 , dims=(2,)) + UNB2TR_I2C_bus_QSFP_error_R = attribute_wrapper(comms_annotation=["UNB2TR_I2C_bus_QSFP_error_R"],datatype=numpy.int64 , dims=(24,2)) + UNB2TR_monitor_rate_RW = attribute_wrapper(comms_annotation=["UNB2TR_monitor_rate_RW" ],datatype=numpy.int64 , access=AttrWriteType.READ_WRITE) + UNB2TR_translator_busy_R = attribute_wrapper(comms_annotation=["UNB2TR_translator_busy_R" ],datatype=numpy.bool_ ) + UNB2_DC_DC_48V_12V_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_DC_DC_48V_12V_IOUT_R" ],datatype=numpy.float64, dims=(2,)) + UNB2_DC_DC_48V_12V_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_DC_DC_48V_12V_TEMP_R" ],datatype=numpy.float64, dims=(2,)) + UNB2_DC_DC_48V_12V_VIN_R = attribute_wrapper(comms_annotation=["UNB2_DC_DC_48V_12V_VIN_R" ],datatype=numpy.float64, dims=(2,)) + UNB2_DC_DC_48V_12V_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_DC_DC_48V_12V_VOUT_R" ],datatype=numpy.float64, dims=(2,)) + UNB2_FPGA_DDR4_SLOT_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_DDR4_SLOT_TEMP_R"],datatype=numpy.float64, dims=(8,2)) + UNB2_FPGA_POL_CORE_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_CORE_IOUT_R" ],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_POL_CORE_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_CORE_TEMP_R" ],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_POL_CORE_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_CORE_VOUT_R" ],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_POL_ERAM_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_ERAM_IOUT_R" ],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_POL_ERAM_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_ERAM_TEMP_R" ],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_POL_ERAM_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_ERAM_VOUT_R" ],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_POL_HGXB_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_HGXB_IOUT_R" ],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_POL_HGXB_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_HGXB_TEMP_R" ],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_POL_HGXB_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_HGXB_VOUT_R" ],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_POL_PGM_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_PGM_IOUT_R" ],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_POL_PGM_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_PGM_TEMP_R" ],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_POL_PGM_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_PGM_VOUT_R" ],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_POL_RXGXB_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_RXGXB_IOUT_R"],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_POL_RXGXB_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_RXGXB_TEMP_R"],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_POL_RXGXB_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_RXGXB_VOUT_R"],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_POL_TXGXB_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_TXGXB_IOUT_R"],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_POL_TXGXB_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_TXGXB_TEMP_R"],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_POL_TXGXB_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_TXGXB_VOUT_R"],datatype=numpy.float64, dims=(4,2)) + UNB2_FPGA_QSFP_CAGE_LOS_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_QSFP_CAGE_LOS_R" ],datatype=numpy.int64 , dims=(24,2)) + UNB2_FPGA_QSFP_CAGE_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_QSFP_CAGE_TEMP_R"],datatype=numpy.float64, dims=(24,2)) + UNB2_Front_Panel_LED_colour_R = attribute_wrapper(comms_annotation=["UNB2_Front_Panel_LED_colour_R"],datatype=numpy.int64 , dims=(2,)) + UNB2_Front_Panel_LED_colour_RW = attribute_wrapper(comms_annotation=["UNB2_Front_Panel_LED_colour_RW"],datatype=numpy.int64 , dims=(2,), access=AttrWriteType.READ_WRITE) + UNB2_mask_RW = attribute_wrapper(comms_annotation=["UNB2_mask_RW" ],datatype=numpy.bool_ , dims=(2,), access=AttrWriteType.READ_WRITE) + UNB2_PCB_ID_R = attribute_wrapper(comms_annotation=["UNB2_PCB_ID_R" ],datatype=numpy.int64 , dims=(2,)) + UNB2_PCB_number_R = attribute_wrapper(comms_annotation=["UNB2_PCB_number_R" ],datatype=numpy.str , dims=(2,)) + UNB2_PCB_version_R = attribute_wrapper(comms_annotation=["UNB2_PCB_version_R" ],datatype=numpy.str , dims=(2,)) + UNB2_POL_CLOCK_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_POL_CLOCK_IOUT_R" ],datatype=numpy.float64, dims=(2,)) + UNB2_POL_CLOCK_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_POL_CLOCK_TEMP_R" ],datatype=numpy.float64, dims=(2,)) + UNB2_POL_CLOCK_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_POL_CLOCK_VOUT_R" ],datatype=numpy.float64, dims=(2,)) + UNB2_POL_QSFP_N01_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_POL_QSFP_N01_IOUT_R" ],datatype=numpy.float64, dims=(2,)) + UNB2_POL_QSFP_N01_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_POL_QSFP_N01_TEMP_R" ],datatype=numpy.float64, dims=(2,)) + UNB2_POL_QSFP_N01_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_POL_QSFP_N01_VOUT_R" ],datatype=numpy.float64, dims=(2,)) + UNB2_POL_QSFP_N23_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_POL_QSFP_N23_IOUT_R" ],datatype=numpy.float64, dims=(2,)) + UNB2_POL_QSFP_N23_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_POL_QSFP_N23_TEMP_R" ],datatype=numpy.float64, dims=(2,)) + UNB2_POL_QSFP_N23_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_POL_QSFP_N23_VOUT_R" ],datatype=numpy.float64, dims=(2,)) + UNB2_POL_SWITCH_1V2_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_POL_SWITCH_1V2_IOUT_R"],datatype=numpy.float64, dims=(2,)) + UNB2_POL_SWITCH_1V2_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_POL_SWITCH_1V2_TEMP_R"],datatype=numpy.float64, dims=(2,)) + UNB2_POL_SWITCH_1V2_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_POL_SWITCH_1V2_VOUT_R"],datatype=numpy.float64, dims=(2,)) + UNB2_POL_SWITCH_PHY_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_POL_SWITCH_PHY_IOUT_R"],datatype=numpy.float64, dims=(2,)) + UNB2_POL_SWITCH_PHY_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_POL_SWITCH_PHY_TEMP_R"],datatype=numpy.float64, dims=(2,)) + UNB2_POL_SWITCH_PHY_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_POL_SWITCH_PHY_VOUT_R"],datatype=numpy.float64, dims=(2,)) + UNB2_PWR_on_R = attribute_wrapper(comms_annotation=["UNB2_PWR_on_R" ],datatype=numpy.bool_ , dims=(2,)) + + # -------- + # overloaded functions + # -------- + + # -------- + # Commands + # -------- + + @command() + @DebugIt() + @only_when_on() + def UNB2_off(self): + """ + + :return:None + """ + self.opcua_connection.call_method(["UNB2_off"]) + + @command() + @DebugIt() + @only_when_on() + def UNB2_on(self): + """ + + :return:None + """ + self.opcua_connection.call_method(["UNB2_on"]) + +# ---------- +# Run server +# ---------- +def main(args=None, **kwargs): + """Main function of the UNB2 module.""" + return entry(UNB2, **kwargs) diff --git a/devices/examples/HW_device_template.py b/tangostationcontrol/tangostationcontrol/examples/HW_device_template.py similarity index 79% rename from devices/examples/HW_device_template.py rename to tangostationcontrol/tangostationcontrol/examples/HW_device_template.py index 6059733023c5ee8448369a706ea2be5c8fa6b840..f7994ec1fea9892a3ae2ab44b57ff87a2b2f1958 100644 --- a/devices/examples/HW_device_template.py +++ b/tangostationcontrol/tangostationcontrol/examples/HW_device_template.py @@ -9,27 +9,21 @@ """ -# TODO(Corne): Remove sys.path.append hack once packaging is in place! -import os, sys -currentdir = os.path.dirname(os.path.realpath(__file__)) -parentdir = os.path.dirname(currentdir) -sys.path.append(parentdir) - # PyTango imports from tango.server import run -from tango import AttrWriteType # Additional import -from clients.attribute_wrapper import attribute_wrapper -from devices.hardware_device import hardware_device +from tangostationcontrol.devices.lofar_device import lofar_device +import logging +logger = logging.getLogger() __all__ = ["HW_dev"] -class HW_dev(hardware_device): +class HW_dev(lofar_device): """ - This class is the minimal (read empty) implementation of a class using 'hardware_device' + This class is the minimal (read empty) implementation of a class using 'lofar_device' """ # ---------- @@ -54,10 +48,10 @@ class HW_dev(hardware_device): init_device method to be released. This method is called by the device destructor and by the device Init command (a Tango built-in). """ - self.debug_stream("Shutting down...") + logger.debug("Shutting down...") self.Off() - self.debug_stream("Shut down. Good bye.") + logger.debug("Shut down. Good bye.") # -------- # overloaded functions @@ -90,7 +84,3 @@ class HW_dev(hardware_device): def main(args=None, **kwargs): """Main function of the hardware device module.""" return run((HW_dev,), args=args, **kwargs) - - -if __name__ == '__main__': - main() diff --git a/devices/test/clients/__init__.py b/tangostationcontrol/tangostationcontrol/examples/__init__.py similarity index 100% rename from devices/test/clients/__init__.py rename to tangostationcontrol/tangostationcontrol/examples/__init__.py diff --git a/devices/test/common/__init__.py b/tangostationcontrol/tangostationcontrol/examples/load_from_disk/__init__.py similarity index 100% rename from devices/test/common/__init__.py rename to tangostationcontrol/tangostationcontrol/examples/load_from_disk/__init__.py diff --git a/devices/examples/load_from_disk/ini_client.py b/tangostationcontrol/tangostationcontrol/examples/load_from_disk/ini_client.py similarity index 93% rename from devices/examples/load_from_disk/ini_client.py rename to tangostationcontrol/tangostationcontrol/examples/load_from_disk/ini_client.py index dcc66a85ac7fae4cbe3d00a47fe46a809618938b..e5fbf03711fe220cfa56fa9327cbede7168b4e86 100644 --- a/devices/examples/load_from_disk/ini_client.py +++ b/tangostationcontrol/tangostationcontrol/examples/load_from_disk/ini_client.py @@ -1,7 +1,10 @@ -from clients.comms_client import CommClient +from tangostationcontrol.clients.comms_client import CommClient import configparser import numpy +import logging +logger = logging.getLogger() + __all__ = ["ini_client"] @@ -25,7 +28,7 @@ ini_to_numpy_dict = { int: numpy.int64, float: numpy.float64, bool: numpy.bool_, - str: numpy.str_ + str: numpy.str } import os @@ -39,14 +42,14 @@ class ini_client(CommClient): def start(self): super().start() - def __init__(self, filename, fault_func, streams, try_interval=2): + def __init__(self, filename, fault_func, try_interval=2): """ initialises the class and tries to connect to the client. """ self.config = configparser.ConfigParser() self.filename = filename - super().__init__(fault_func, streams, try_interval) + super().__init__(fault_func, try_interval) # Explicitly connect if not self.connect(): @@ -62,7 +65,7 @@ class ini_client(CommClient): def disconnect(self): self.connected = False # always force a reconnect, regardless of a successful disconnect - self.streams.debug_stream("disconnected from the 'client' ") + logger.debug("disconnected from the 'client' ") def _setup_annotation(self, annotation): """ @@ -81,7 +84,7 @@ class ini_client(CommClient): """ # as this is an example, just print the annotation - self.streams.debug_stream("annotation: {}".format(annotation)) + logger.debug("annotation: {}".format(annotation)) name = annotation.get('name') if name is None: ValueError("ini client requires a variable `name` in the annotation to set/get") @@ -171,9 +174,9 @@ def data_handler(string, dtype): value = dtype(value) - elif dtype is numpy.str_: + elif dtype is numpy.str: for i in string.split(","): - val = numpy.str_(i) + val = numpy.str(i) value.append(val) value = numpy.array(value) diff --git a/devices/examples/load_from_disk/ini_device.py b/tangostationcontrol/tangostationcontrol/examples/load_from_disk/ini_device.py similarity index 83% rename from devices/examples/load_from_disk/ini_device.py rename to tangostationcontrol/tangostationcontrol/examples/load_from_disk/ini_device.py index e4aaef9063b16d94b63822d742bcd10bbef8d35f..032c9b01b2a5447111d6245ffcba1bd610b3b655 100644 --- a/devices/examples/load_from_disk/ini_device.py +++ b/tangostationcontrol/tangostationcontrol/examples/load_from_disk/ini_device.py @@ -9,25 +9,20 @@ """ -# TODO(Corne): Remove sys.path.append hack once packaging is in place! -import os, sys -currentdir = os.path.dirname(os.path.realpath(__file__)) -parentdir = os.path.dirname(currentdir) -parentdir = os.path.dirname(parentdir) -sys.path.append(parentdir) - # PyTango imports from tango.server import run from tango import AttrWriteType -# Additional import -from clients.attribute_wrapper import attribute_wrapper -from devices.hardware_device import hardware_device - import configparser import numpy -from ini_client import * +# Additional import +from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper +from tangostationcontrol.devices.lofar_device import lofar_device +from tangostationcontrol.examples.load_from_disk.ini_client import * + +import logging +logger = logging.getLogger() __all__ = ["ini_device"] @@ -59,9 +54,9 @@ def write_ini_file(filename): -class ini_device(hardware_device): +class ini_device(lofar_device): """ - This class is the minimal (read empty) implementation of a class using 'hardware_device' + This class is the minimal (read empty) implementation of a class using 'lofar_device' """ # ---------- @@ -80,8 +75,8 @@ class ini_device(hardware_device): bool_scalar_R = attribute_wrapper(comms_annotation={"section": "scalar", "name": "bool_scalar_R"}, datatype=numpy.bool_) int_scalar_RW = attribute_wrapper(comms_annotation={"section": "scalar", "name": "int_scalar_RW"}, datatype=numpy.int64, access=AttrWriteType.READ_WRITE) int_scalar_R = attribute_wrapper(comms_annotation={"section": "scalar", "name": "int_scalar_R"}, datatype=numpy.int64) - str_scalar_RW = attribute_wrapper(comms_annotation={"section": "scalar", "name": "str_scalar_RW"}, datatype=numpy.str_, access=AttrWriteType.READ_WRITE) - str_scalar_R = attribute_wrapper(comms_annotation={"section": "scalar", "name": "str_scalar_R"}, datatype=numpy.str_) + str_scalar_RW = attribute_wrapper(comms_annotation={"section": "scalar", "name": "str_scalar_RW"}, datatype=numpy.str, access=AttrWriteType.READ_WRITE) + str_scalar_R = attribute_wrapper(comms_annotation={"section": "scalar", "name": "str_scalar_R"}, datatype=numpy.str) double_spectrum_RW = attribute_wrapper(comms_annotation={"section": "spectrum", "name": "double_spectrum_RW"}, datatype=numpy.double, dims=(4,), access=AttrWriteType.READ_WRITE) double_spectrum_R = attribute_wrapper(comms_annotation={"section": "spectrum", "name": "double_spectrum_R"}, datatype=numpy.double, dims=(4,)) @@ -89,8 +84,8 @@ class ini_device(hardware_device): bool_spectrum_R = attribute_wrapper(comms_annotation={"section": "spectrum", "name": "bool_spectrum_R"}, datatype=numpy.bool_, dims=(4,)) int_spectrum_RW = attribute_wrapper(comms_annotation={"section": "spectrum", "name": "int_spectrum_RW"}, datatype=numpy.int64, dims=(4,), access=AttrWriteType.READ_WRITE) int_spectrum_R = attribute_wrapper(comms_annotation={"section": "spectrum", "name": "int_spectrum_R"}, datatype=numpy.int64, dims=(4,)) - str_spectrum_RW = attribute_wrapper(comms_annotation={"section": "spectrum", "name": "str_spectrum_RW"}, datatype=numpy.str_, dims=(4,), access=AttrWriteType.READ_WRITE) - str_spectrum_R = attribute_wrapper(comms_annotation={"section": "spectrum", "name": "str_spectrum_R"}, datatype=numpy.str_, dims=(4,)) + str_spectrum_RW = attribute_wrapper(comms_annotation={"section": "spectrum", "name": "str_spectrum_RW"}, datatype=numpy.str, dims=(4,), access=AttrWriteType.READ_WRITE) + str_spectrum_R = attribute_wrapper(comms_annotation={"section": "spectrum", "name": "str_spectrum_R"}, datatype=numpy.str, dims=(4,)) double_image_RW = attribute_wrapper(comms_annotation={"section": "image", "name": "double_image_RW"}, datatype=numpy.double, dims=(3, 2), access=AttrWriteType.READ_WRITE) double_image_R = attribute_wrapper(comms_annotation={"section": "image", "name": "double_image_R"}, datatype=numpy.double, dims=(3, 2)) @@ -98,15 +93,15 @@ class ini_device(hardware_device): bool_image_R = attribute_wrapper(comms_annotation={"section": "image", "name": "bool_image_R"}, datatype=numpy.bool_, dims=(3, 2)) int_image_RW = attribute_wrapper(comms_annotation={"section": "image", "name": "int_image_RW"}, datatype=numpy.int64, dims=(3, 2), access=AttrWriteType.READ_WRITE) int_image_R = attribute_wrapper(comms_annotation={"section": "image", "name": "int_image_R"}, datatype=numpy.int64, dims=(3, 2)) - str_image_RW = attribute_wrapper(comms_annotation={"section": "image", "name": "str_image_RW"}, datatype=numpy.str_, dims=(3, 2), access=AttrWriteType.READ_WRITE) - str_image_R = attribute_wrapper(comms_annotation={"section": "image", "name": "str_image_R"}, datatype=numpy.str_, dims=(3, 2)) + str_image_RW = attribute_wrapper(comms_annotation={"section": "image", "name": "str_image_RW"}, datatype=numpy.str, dims=(3, 2), access=AttrWriteType.READ_WRITE) + str_image_R = attribute_wrapper(comms_annotation={"section": "image", "name": "str_image_R"}, datatype=numpy.str, dims=(3, 2)) # -------- # overloaded functions # -------- def configure_for_initialise(self): """ user code here. is called when the sate is set to INIT """ - """Initialises the attributes and properties of the PCC.""" + """Initialises the attributes and properties of the Hardware.""" # set up the OPC ua client self.ini_client = ini_client("example.ini", self.Fault, self) @@ -119,7 +114,7 @@ class ini_device(hardware_device): # use the pass function instead of setting read/write fails i.set_pass_func() - self.warn_stream("error while setting the ini attribute {} read/write function. {}".format(i, e)) + logger.warning("error while setting the ini attribute {} read/write function. {}".format(i, e)) self.ini_client.start() @@ -130,10 +125,5 @@ class ini_device(hardware_device): def main(args=None, **kwargs): write_ini_file("example.ini") - """Main function of the hardware device module.""" return run((ini_device,), args=args, **kwargs) - - -if __name__ == '__main__': - main() diff --git a/devices/test/devices/__init__.py b/tangostationcontrol/tangostationcontrol/examples/snmp/__init__.py similarity index 100% rename from devices/test/devices/__init__.py rename to tangostationcontrol/tangostationcontrol/examples/snmp/__init__.py diff --git a/devices/examples/snmp/snmp.py b/tangostationcontrol/tangostationcontrol/examples/snmp/snmp.py similarity index 71% rename from devices/examples/snmp/snmp.py rename to tangostationcontrol/tangostationcontrol/examples/snmp/snmp.py index b54c4fe9033d7ec52236f3df74b57874bac1204f..3c962da9911abf9a0b9cbb4d7ecd4ff19c6e95d5 100644 --- a/devices/examples/snmp/snmp.py +++ b/tangostationcontrol/tangostationcontrol/examples/snmp/snmp.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# This file is part of the PCC project +# This file is part of theRECV project # # # @@ -11,29 +11,25 @@ """ -# TODO(Corne): Remove sys.path.append hack once packaging is in place! -import os, sys -currentdir = os.path.dirname(os.path.realpath(__file__)) -parentdir = os.path.dirname(currentdir) -parentdir = os.path.dirname(parentdir) -sys.path.append(parentdir) - # PyTango imports from tango.server import run from tango.server import device_property from tango import AttrWriteType # Additional import -from examples.snmp.snmp_client import SNMP_client -from clients.attribute_wrapper import attribute_wrapper -from devices.hardware_device import hardware_device +from tangostationcontrol.examples.snmp.snmp_client import SNMP_client +from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper +from tangostationcontrol.devices.lofar_device import lofar_device import numpy +import logging +logger = logging.getLogger() + __all__ = ["SNMP", "main"] -class SNMP(hardware_device): +class SNMP(lofar_device): """ **Properties:** @@ -70,15 +66,15 @@ class SNMP(hardware_device): # Attributes # ---------- - sys_description_R = attribute_wrapper(comms_annotation={"oids": "1.3.6.1.2.1.1.1.0"}, datatype=numpy.str_) - sys_objectID_R = attribute_wrapper(comms_annotation={"oids": "1.3.6.1.2.1.1.2.0", "type": "OID"}, datatype=numpy.str_) + sys_description_R = attribute_wrapper(comms_annotation={"oids": "1.3.6.1.2.1.1.1.0"}, datatype=numpy.str) + sys_objectID_R = attribute_wrapper(comms_annotation={"oids": "1.3.6.1.2.1.1.2.0", "type": "OID"}, datatype=numpy.str) sys_uptime_R = attribute_wrapper(comms_annotation={"oids": "1.3.6.1.2.1.1.3.0", "type": "TimeTicks"}, datatype=numpy.int64) - sys_name_R = attribute_wrapper(comms_annotation={"oids": "1.3.6.1.2.1.1.5.0"}, datatype=numpy.str_) - ip_route_mask_127_0_0_1_R = attribute_wrapper(comms_annotation={"oids": "1.3.6.1.2.1.4.21.1.11.127.0.0.1", "type": "IpAddress"}, datatype=numpy.str_) + sys_name_R = attribute_wrapper(comms_annotation={"oids": "1.3.6.1.2.1.1.5.0"}, datatype=numpy.str) + ip_route_mask_127_0_0_1_R = attribute_wrapper(comms_annotation={"oids": "1.3.6.1.2.1.4.21.1.11.127.0.0.1", "type": "IpAddress"}, datatype=numpy.str) TCP_active_open_R = attribute_wrapper(comms_annotation={"oids": "1.3.6.1.2.1.6.5.0", "type": "Counter32"}, datatype=numpy.int64) - sys_contact_RW = attribute_wrapper(comms_annotation={"oids": "1.3.6.1.2.1.1.4.0"}, datatype=numpy.str_, access=AttrWriteType.READ_WRITE) - sys_contact_R = attribute_wrapper(comms_annotation={"oids": "1.3.6.1.2.1.1.4.0"}, datatype=numpy.str_) + sys_contact_RW = attribute_wrapper(comms_annotation={"oids": "1.3.6.1.2.1.1.4.0"}, datatype=numpy.str, access=AttrWriteType.READ_WRITE) + sys_contact_R = attribute_wrapper(comms_annotation={"oids": "1.3.6.1.2.1.1.4.0"}, datatype=numpy.str) TCP_Curr_estab_R = attribute_wrapper(comms_annotation={"oids": "1.3.6.1.2.1.6.9.0", "type": "Gauge"}, datatype=numpy.int64) @@ -102,7 +98,7 @@ class SNMP(hardware_device): except Exception as e: # use the pass function instead of setting read/write fails i.set_pass_func() - self.warn_stream("error while setting the SNMP attribute {} read/write function. {}".format(i, e)) + logger.warning("error while setting the SNMP attribute {} read/write function. {}".format(i, e)) self.snmp_manager.start() @@ -116,14 +112,9 @@ class SNMP(hardware_device): # Run server # ---------- def main(args=None, **kwargs): - """Main function of the PCC module.""" + """Main function of the module.""" - from common.lofar_logging import configure_logger - import logging - configure_logger(logging.getLogger()) + from tangostationcontrol.common.lofar_logging import configure_logger + configure_logger() return run((SNMP,), args=args, **kwargs) - - -if __name__ == '__main__': - main() diff --git a/devices/examples/snmp/snmp_client.py b/tangostationcontrol/tangostationcontrol/examples/snmp/snmp_client.py similarity index 89% rename from devices/examples/snmp/snmp_client.py rename to tangostationcontrol/tangostationcontrol/examples/snmp/snmp_client.py index 2c162abef0f924c3d67d9d248253c2a9df533a3f..7c18baa9a6954e03ab07eb85fe35b6e342867650 100644 --- a/devices/examples/snmp/snmp_client.py +++ b/tangostationcontrol/tangostationcontrol/examples/snmp/snmp_client.py @@ -1,9 +1,12 @@ -from clients.comms_client import CommClient + +from tangostationcontrol.clients.comms_client import CommClient import snmp import numpy -import traceback +import logging + +logger = logging.getLogger() __all__ = ["SNMP_client"] @@ -11,11 +14,11 @@ __all__ = ["SNMP_client"] snmp_to_numpy_dict = { snmp.types.INTEGER: numpy.int64, snmp.types.TimeTicks: numpy.int64, - snmp.types.OCTET_STRING: numpy.str_, - snmp.types.OID: numpy.str_, + snmp.types.OCTET_STRING: numpy.str, + snmp.types.OID: numpy.str, snmp.types.Counter32: numpy.int64, snmp.types.Gauge32: numpy.int64, - snmp.types.IpAddress: numpy.str_, + snmp.types.IpAddress: numpy.str, } snmp_types = { @@ -23,9 +26,9 @@ snmp_types = { "Gauge": numpy.int64, "TimeTick": numpy.int64, "Counter32": numpy.int64, - "OctetString": numpy.str_, - "IpAddress": numpy.str_, - "OID": numpy.str_, + "OctetString": numpy.str, + "IpAddress": numpy.str, + "OID": numpy.str, } @@ -37,11 +40,11 @@ class SNMP_client(CommClient): def start(self): super().start() - def __init__(self, community, host, timeout, fault_func, streams, try_interval=2): + def __init__(self, community, host, timeout, fault_func, try_interval=2): """ Create the SNMP and connect() to it """ - super().__init__(fault_func, streams, try_interval) + super().__init__(fault_func, try_interval) self.community = community self.host = host @@ -57,7 +60,7 @@ class SNMP_client(CommClient): """ Try to connect to the client """ - self.streams.debug_stream("Connecting to community: %s, host: %s", self.community, self.host) + logger.debug("Connecting to community: %s, host: %s", self.community, self.host) self.connected = True return True diff --git a/tangostationcontrol/tangostationcontrol/integration_test/README.md b/tangostationcontrol/tangostationcontrol/integration_test/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6609a2da61665aab3d93c9f4d716148e03428fba --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/integration_test/README.md @@ -0,0 +1,25 @@ +# Integration Tests + +## Approach + +A special docker container is build to perform the integration tests. This +container will be build by the makefiles but should only be started by the +dedicated integration test script. This script will ensure that other containers +are running and are in the required state. + +* Launch recv-sim and sdptr-sim simulators. +* Reconfigure dsconfig to use these simulators. +* Create and start the integration-test container. + +## Running + +**Warning running these tests will make changes to your CDB database config!** + +```shell +sbin/run_integration_test.sh +``` + +## Limitations + +Our makefile will always launch the new container upon creation, resulting in +the integration tests actually being run twice. diff --git a/devices/util/__init__.py b/tangostationcontrol/tangostationcontrol/integration_test/__init__.py similarity index 100% rename from devices/util/__init__.py rename to tangostationcontrol/tangostationcontrol/integration_test/__init__.py diff --git a/tangostationcontrol/tangostationcontrol/integration_test/base.py b/tangostationcontrol/tangostationcontrol/integration_test/base.py new file mode 100644 index 0000000000000000000000000000000000000000..ed1eb2239af74251e22b42adf8ea5e2596688076 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/integration_test/base.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +from tangostationcontrol.common.lofar_logging import configure_logger + +import unittest +import asynctest +import testscenarios + +"""Setup logging for integration tests""" +configure_logger(debug=True) + + +class BaseIntegrationTestCase(testscenarios.WithScenarios, unittest.TestCase): + """Integration test base class.""" + + def setUp(self): + super().setUp() + + +class IntegrationTestCase(BaseIntegrationTestCase): + """Integration test case base class for all unit tests.""" + + def setUp(self): + super().setUp() + +class IntegrationAsyncTestCase(testscenarios.WithScenarios, asynctest.TestCase): + """Integration test case base class for all asyncio unit tests.""" + + def setUp(self): + super().setUp() diff --git a/tangostationcontrol/tangostationcontrol/integration_test/client/__init__.py b/tangostationcontrol/tangostationcontrol/integration_test/client/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tangostationcontrol/tangostationcontrol/integration_test/client/test_opcua_client_against_server.py b/tangostationcontrol/tangostationcontrol/integration_test/client/test_opcua_client_against_server.py new file mode 100644 index 0000000000000000000000000000000000000000..bdd4e43b94b1a98596f339e220ee31116818fc37 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/integration_test/client/test_opcua_client_against_server.py @@ -0,0 +1,163 @@ +import asyncua +import numpy + +from tangostationcontrol.clients.opcua_client import OPCUAConnection + +from tangostationcontrol.integration_test import base + + +class TestClientServer(base.IntegrationAsyncTestCase): + """ Test the OPCUAConnection against an OPCUA server we instantiate ourselves. """ + + async def setup_server(self, port): + """ Setup a server on a dedicated port for the test, to allow + the tests to be run in parallel. """ + + # where we will run the server + self.endpoint = f"opc.tcp://127.0.0.1:{port}" + self.namespace = "http://example.com" + + # setup an OPC-UA server + self.server = asyncua.Server() + await self.server.init() + self.server.set_endpoint(self.endpoint) + self.server.set_server_name(f"Test server spawned by {__file__}") + + # create an interface + idx = await self.server.register_namespace(self.namespace) + obj = self.server.get_objects_node() + + # add double_R/double_RW + double_R = await obj.add_variable(idx, "double_R", 42.0) + double_RW = await obj.add_variable(idx, "double_RW", 42.0) + await double_RW.set_writable() + + # add methods + @asyncua.uamethod + def multiply(parent, x, y): + self.assertEqual(float, type(x)) + self.assertEqual(int, type(y)) + return x * y + + @asyncua.uamethod + def procedure(parent): + return + + @asyncua.uamethod + def throws(parent): + raise Exception("Expected test exception") + + multiply_method = await obj.add_method(idx, "multiply", multiply, [asyncua.ua.VariantType.Double, asyncua.ua.VariantType.Int64], [asyncua.ua.VariantType.Double]) + procedure_method = await obj.add_method(idx, "procedure", procedure, [], []) + throws_method = await obj.add_method(idx, "throws", throws, [], []) + + # run the server + await self.server.start() + + async def setUp(self): + self.server = None + + async def tearDown(self): + if self.server: + await self.server.stop() + + def fault_func(self): + raise Exception("FAULT") + + async def test_opcua_connection(self): + await self.setup_server(14840) + + test_client = OPCUAConnection(self.endpoint, self.namespace, 5, self.fault_func, self.loop) + try: + await test_client.start() + finally: + await test_client.stop() + + async def test_read_attribute(self): + await self.setup_server(14841) + + test_client = OPCUAConnection(self.endpoint, self.namespace, 5, self.fault_func, self.loop) + try: + await test_client.start() + + # setup the attribute + class attribute(object): + dim_x = 1 + dim_y = 0 + numpy_type = numpy.double + + prot_attr = await test_client.setup_protocol_attribute(["double_R"], attribute()) + + # read it from the server + self.assertEqual(42.0, await prot_attr.read_function()) + finally: + await test_client.stop() + + async def test_write_attribute(self): + await self.setup_server(14842) + + test_client = OPCUAConnection(self.endpoint, self.namespace, 5, self.fault_func, self.loop) + try: + await test_client.start() + + # setup the attribute + class attribute(object): + dim_x = 1 + dim_y = 0 + numpy_type = numpy.double + + prot_attr = await test_client.setup_protocol_attribute(["double_RW"], attribute()) + + # write it to the server and read it back to verify + await prot_attr.write_function(123.0) + + self.assertEqual(123.0, await prot_attr.read_function()) + finally: + await test_client.stop() + + async def test_method_without_args(self): + await self.setup_server(14843) + + test_client = OPCUAConnection(self.endpoint, self.namespace, 5, self.fault_func, self.loop) + try: + await test_client.start() + + self.assertEqual(None, await test_client._call_method(["procedure"])) + finally: + await test_client.stop() + + async def test_method_with_args(self): + await self.setup_server(14843) + + test_client = OPCUAConnection(self.endpoint, self.namespace, 5, self.fault_func, self.loop) + try: + await test_client.start() + + self.assertEqual(21.0, await test_client._call_method(["multiply"], numpy.double(3.0), numpy.int64(7))) + finally: + await test_client.stop() + + async def test_method_with_wrong_arg_types(self): + await self.setup_server(14844) + + test_client = OPCUAConnection(self.endpoint, self.namespace, 5, self.fault_func, self.loop) + try: + await test_client.start() + + with self.assertRaises(Exception): + # correct signature is multiply(double,int64) + _ = await test_client._call_method(["multiply"], numpy.double(3.0), numpy.double(7)) + finally: + await test_client.stop() + + async def test_errorring_method(self): + await self.setup_server(14845) + + test_client = OPCUAConnection(self.endpoint, self.namespace, 5, self.fault_func, self.loop) + try: + await test_client.start() + + with self.assertRaises(Exception): + await test_client._call_method(["throws"]) + finally: + await test_client.stop() diff --git a/tangostationcontrol/tangostationcontrol/integration_test/client/test_sdptr_sim.py b/tangostationcontrol/tangostationcontrol/integration_test/client/test_sdptr_sim.py new file mode 100644 index 0000000000000000000000000000000000000000..a09f407e2982d7b873021f03b1eb9e78fe336e44 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/integration_test/client/test_sdptr_sim.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +from asyncua import Client + +from tangostationcontrol.integration_test import base + + +class TestSDPTRSim(base.IntegrationAsyncTestCase): + + def setUp(self): + super(TestSDPTRSim, self).setUp() + + async def test_opcua_connection(self): + """Check if we can connect to sdptr-sim""" + + client = Client("opc.tcp://sdptr-sim:4840") + root_node = None + + try: + await client.connect() + root_node = client.get_root_node() + finally: + await client.disconnect() + + self.assertNotEqual(None, root_node) diff --git a/tangostationcontrol/tangostationcontrol/integration_test/client/test_tcp_replicator.py b/tangostationcontrol/tangostationcontrol/integration_test/client/test_tcp_replicator.py new file mode 100644 index 0000000000000000000000000000000000000000..2467fd6dd8a7b24cc786c5b2cc10a25a610b88f1 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/integration_test/client/test_tcp_replicator.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +import logging +import time +import socket +import sys + +import timeout_decorator + +from tangostationcontrol.clients.tcp_replicator import TCPReplicator + +from tangostationcontrol.integration_test import base + +logger = logging.getLogger() + + +class TestTCPReplicator(base.IntegrationTestCase): + + def setUp(self): + + super(TestTCPReplicator, self).setUp() + + def test_start_stop(self): + """Test start and stopping the server gracefully""" + + test_options = { + "tcp_port": 56565, # Pick some port with low change of collision + } + + replicator = TCPReplicator(test_options) + self.assertTrue(replicator.is_alive()) + + def test_start_except(self): + """Test start and stopping the server gracefully""" + + test_options = { + "tcp_port": 56566, # Pick some port with low change of collision + } + + replicator = TCPReplicator(test_options) + self.assertTrue(replicator.is_alive()) + + self.assertRaises(RuntimeError, TCPReplicator, test_options) + + def test_start_transmit_empty_stop(self): + """Test transmitting without clients""" + + test_options = { + "tcp_port": 56567, # Pick some port with low change of collision + } + + replicator = TCPReplicator(test_options) + self.assertTrue(replicator.is_alive()) + + replicator.transmit("Hello World!".encode('utf-8')) + + def test_start_connect_close(self): + test_options = { + "tcp_port": 56568, # Pick some port with low change of collision + } + + replicator = TCPReplicator(test_options) + self.assertTrue(replicator.is_alive()) + + time.sleep(2) + + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(("127.0.0.1", test_options['tcp_port'])) + + time.sleep(2) + + replicator.join() + + self.assertEquals(b'', s.recv(9000)) + + @timeout_decorator.timeout(15) + def test_start_connect_receive(self): + test_options = { + "tcp_port": 56569, # Pick some port with low change of collision + } + + m_data = "hello world".encode("utf-8") + + replicator = TCPReplicator(test_options) + self.assertTrue(replicator.is_alive()) + + time.sleep(2) + + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(("127.0.0.1", test_options['tcp_port'])) + + time.sleep(2) + + replicator.transmit(m_data) + + data = s.recv(sys.getsizeof(m_data)) + s.close() + + self.assertEqual(m_data, data) + + @timeout_decorator.timeout(15) + def test_start_connect_receive_multiple(self): + test_options = { + "tcp_port": 56570, # Pick some port with low change of collision + } + + m_data = "hello world".encode("utf-8") + + replicator = TCPReplicator(test_options) + self.assertTrue(replicator.is_alive()) + + time.sleep(2) + + s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s1.connect(("127.0.0.1", test_options['tcp_port'])) + + s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s2.connect(("127.0.0.1", test_options['tcp_port'])) + + time.sleep(3) + + replicator.transmit(m_data) + + data1 = s1.recv(sys.getsizeof(m_data)) + s1.close() + + data2 = s2.recv(sys.getsizeof(m_data)) + s2.close() + + self.assertEqual(m_data, data1) + self.assertEqual(m_data, data2) + + @timeout_decorator.timeout(15) + def test_start_connect_receive_multiple_queue(self): + test_options = { + "tcp_port": 56571, # Pick some port with low change of collision + } + + m_data = "hello world".encode("utf-8") + + replicator = TCPReplicator(test_options) + self.assertTrue(replicator.is_alive()) + + time.sleep(2) + + s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s1.connect(("127.0.0.1", test_options['tcp_port'])) + + s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s2.connect(("127.0.0.1", test_options['tcp_port'])) + + time.sleep(3) + + replicator.put(m_data) + + data1 = s1.recv(sys.getsizeof(m_data)) + s1.close() + + data2 = s2.recv(sys.getsizeof(m_data)) + s2.close() + + self.assertEqual(m_data, data1) + self.assertEqual(m_data, data2) diff --git a/tangostationcontrol/tangostationcontrol/integration_test/client/test_unb2_sim.py b/tangostationcontrol/tangostationcontrol/integration_test/client/test_unb2_sim.py new file mode 100644 index 0000000000000000000000000000000000000000..261441901c589a26256b586dc571f7b063c08408 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/integration_test/client/test_unb2_sim.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +from asyncua import Client + +from tangostationcontrol.integration_test import base + + +class TestUNB2Sim(base.IntegrationAsyncTestCase): + + def setUp(self): + super(TestUNB2Sim, self).setUp() + + async def test_opcua_connection(self): + """Check if we can connect to unb2-sim""" + + client = Client("opc.tcp://unb2-sim:4841") + root_node = None + + await client.connect() + + try: + root_node = client.get_root_node() + finally: + await client.disconnect() + + self.assertNotEqual(None, root_node) diff --git a/tangostationcontrol/tangostationcontrol/integration_test/device_proxy.py b/tangostationcontrol/tangostationcontrol/integration_test/device_proxy.py new file mode 100644 index 0000000000000000000000000000000000000000..00ba0904c7bd01fa2ce1453a4c6701d6a4246e14 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/integration_test/device_proxy.py @@ -0,0 +1,8 @@ +from tango import DeviceProxy + + +class TestDeviceProxy(DeviceProxy): + + def __init__(self, *args, **kwargs): + super(TestDeviceProxy, self).__init__(*args, **kwargs) + self.set_timeout_millis(10000) diff --git a/tangostationcontrol/tangostationcontrol/integration_test/devices/__init__.py b/tangostationcontrol/tangostationcontrol/integration_test/devices/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_recv.py b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_recv.py new file mode 100644 index 0000000000000000000000000000000000000000..6d9353936537a116442409b9ce7c343fa4d803b1 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_recv.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +from tango._tango import DevState + +from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy +from tangostationcontrol.integration_test import base + + +class TestDeviceRECV(base.IntegrationTestCase): + + def setUp(self): + super(TestDeviceRECV, self).setUp() + + def tearDown(self): + """Turn device Off in teardown to prevent blocking tests""" + d = TestDeviceProxy("STAT/RECV/1") + + try: + d.Off() + except Exception as e: + """Failing to turn Off devices should not raise errors here""" + print(f"Failed to turn device off in teardown {e}") + + def test_device_proxy_recv(self): + """Test if we can successfully create a DeviceProxy and fetch state""" + + d = TestDeviceProxy("STAT/RECV/1") + + self.assertEqual(DevState.OFF, d.state()) + + def test_device_recv_initialize(self): + """Test if we can transition to standby""" + + d = TestDeviceProxy("STAT/RECV/1") + + d.Initialise() + + self.assertEqual(DevState.STANDBY, d.state()) + + def test_device_recv_on(self): + """Test if we can transition to on""" + + d = TestDeviceProxy("STAT/RECV/1") + + d.Initialise() + + d.on() + + self.assertEqual(DevState.ON, d.state()) diff --git a/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_sdp.py b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_sdp.py new file mode 100644 index 0000000000000000000000000000000000000000..f12fc0ae4f9094c6c82a2dd9076f49cea7fd7ef1 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_sdp.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +from tango import DeviceProxy +from tango._tango import DevState + +from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy +from tangostationcontrol.integration_test import base + + +class TestDeviceSDP(base.IntegrationTestCase): + + def setUp(self): + """Intentionally recreate the device object in each test""" + super(TestDeviceSDP, self).setUp() + + def tearDown(self): + """Turn device Off in teardown to prevent blocking tests""" + d = TestDeviceProxy("STAT/SDP/1") + + try: + d.Off() + except Exception as e: + """Failing to turn Off devices should not raise errors here""" + print(f"Failed to turn device off in teardown {e}") + + def test_device_proxy_sdp(self): + """Test if we can successfully create a DeviceProxy and fetch state""" + + d = TestDeviceProxy("STAT/SDP/1") + + self.assertEqual(DevState.OFF, d.state()) + + def test_device_sdp_ping(self): + """Test if we can successfully ping the device server""" + + d = TestDeviceProxy("STAT/SDP/1") + + self.assertGreater(d.ping(), 0) + + def test_device_sdp_initialize(self): + """Test if we can transition to standby""" + + d = TestDeviceProxy("STAT/SDP/1") + + d.Initialise() + + self.assertEqual(DevState.STANDBY, d.state()) + + def test_device_sdp_on(self): + """Test if we can transition to on""" + + d = TestDeviceProxy("STAT/SDP/1") + + d.Initialise() + + d.on() + + self.assertEqual(DevState.ON, d.state()) + + def test_device_sdp_read_attribute(self): + """Test if we can read an attribute obtained over OPC-UA""" + + d = TestDeviceProxy("STAT/SDP/1") + + d.initialise() + + d.on() + + self.assertListEqual([True]*16, list(d.TR_fpga_communication_error_R)) diff --git a/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_sst.py b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_sst.py new file mode 100644 index 0000000000000000000000000000000000000000..210dc93defc98c51f9fb9a2b0497a52e5547e5a7 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_sst.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. +import socket +import sys +import time + +from tango._tango import DevState + +from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy +from tangostationcontrol.integration_test import base + + +class TestDeviceSST(base.IntegrationTestCase): + + def setUp(self): + """Intentionally recreate the device object in each test""" + super(TestDeviceSST, self).setUp() + + def tearDown(self): + """Turn device Off in teardown to prevent blocking tests""" + d = TestDeviceProxy("STAT/SST/1") + + try: + d.Off() + except Exception as e: + """Failing to turn Off devices should not raise errors here""" + print(f"Failed to turn device off in teardown {e}") + + def test_device_proxy_sst(self): + """Test if we can successfully create a DeviceProxy and fetch state""" + + d = TestDeviceProxy("STAT/SST/1") + + self.assertEqual(DevState.OFF, d.state()) + + def test_device_sst_initialize(self): + """Test if we can transition to standby""" + + d = TestDeviceProxy("STAT/SST/1") + + d.initialise() + + self.assertEqual(DevState.STANDBY, d.state()) + + def test_device_sst_on(self): + """Test if we can transition to on""" + + port_property = {"Statistics_Client_TCP_Port": "4999"} + + d = TestDeviceProxy("STAT/SST/1") + + self.assertEqual(DevState.OFF, d.state(), + "Prerequisite could not be met " + "this test can not continue") + + d.put_property(port_property) + + d.initialise() + + self.assertEqual(DevState.STANDBY, d.state()) + + d.on() + + self.assertEqual(DevState.ON, d.state()) + + def test_device_sst_send_udp(self): + port_property = {"Statistics_Client_TCP_Port": "4998"} + + d = TestDeviceProxy("STAT/SST/1") + + self.assertEqual(DevState.OFF, d.state(), + "Prerequisite could not be met " + "this test can not continue") + + d.put_property(port_property) + + d.initialise() + + self.assertEqual(DevState.STANDBY, d.state()) + + d.on() + + self.assertEqual(DevState.ON, d.state()) + + s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s1.connect(("device-sst", 5001)) + + # TODO(Corne): Change me into an actual SST packet + s1.send("Hello World!".encode("UTF-8")) + + s1.close() + + def test_device_sst_connect_tcp_receive(self): + port_property = {"Statistics_Client_TCP_Port": "5101"} + + m_data = "Hello World!".encode("UTF-8") + + d = TestDeviceProxy("STAT/SST/1") + + self.assertEqual(DevState.OFF, d.state(), + "Prerequisite could not be met " + "this test can not continue") + + d.put_property(port_property) + + d.initialise() + + self.assertEqual(DevState.STANDBY, d.state()) + + d.on() + + self.assertEqual(DevState.ON, d.state()) + + time.sleep(2) + + s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s1.connect(("device-sst", 5001)) + + s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s2.connect(("device-sst", 5101)) + + time.sleep(2) + + # TODO(Corne): Change me into an actual SST packet + s1.send(m_data) + + time.sleep(2) + + data = s2.recv(sys.getsizeof(m_data)) + + s1.close() + s2.close() + + self.assertEqual(m_data, data) diff --git a/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_unb2.py b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_unb2.py new file mode 100644 index 0000000000000000000000000000000000000000..4a0382f2aea7c370562ff4d23e37c85c7963a436 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_unb2.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +from tango._tango import DevState + +from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy +from tangostationcontrol.integration_test import base + + +class TestDeviceUNB2(base.IntegrationTestCase): + + def setUp(self): + """Intentionally recreate the device object in each test""" + super(TestDeviceUNB2, self).setUp() + + def tearDown(self): + """Turn device Off in teardown to prevent blocking tests""" + d = TestDeviceProxy("STAT/UNB2/1") + + try: + d.Off() + except Exception as e: + """Failing to turn Off devices should not raise errors here""" + print(f"Failed to turn device off in teardown {e}") + + def test_device_proxy_unb2(self): + """Test if we can successfully create a DeviceProxy and fetch state""" + + d = TestDeviceProxy("STAT/UNB2/1") + + self.assertEqual(DevState.OFF, d.state()) + + def test_device_unb2_initialize(self): + """Test if we can transition to standby""" + + d = TestDeviceProxy("STAT/UNB2/1") + + d.initialise() + + self.assertEqual(DevState.STANDBY, d.state()) + + def test_device_unb2_on(self): + """Test if we can transition to on""" + + d = TestDeviceProxy("STAT/UNB2/1") + + d.initialise() + + d.on() + + self.assertEqual(DevState.ON, d.state()) diff --git a/tangostationcontrol/tangostationcontrol/integration_test/devices/test_tango_database.py b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_tango_database.py new file mode 100644 index 0000000000000000000000000000000000000000..b14cc363f24b3ea8e77ecd59e1184500fe40e0d4 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_tango_database.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +import time + +from tango import Database +from tango._tango import DevState + +from tangostationcontrol.integration_test import base + + +class TestTangoDatabase(base.IntegrationTestCase): + + def setUp(self): + """Intentionally recreate the device object in each test""" + super(TestTangoDatabase, self).setUp() + + def test_database_servers(self): + """Connect to the database and find at least 3 servers + + One for SDP, RECV and the databaseds itself. + """ + + d = Database() + + # Ensure this value is close to actual amount of servers defined by + # integration_ConfigDb.json + self.assertGreater(len(d.get_server_list()), 16, msg=f"Servers: {d.get_server_list()}") diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/README.md b/tangostationcontrol/tangostationcontrol/statistics_writer/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9c3e24a6ed360701778e023a9cc42d46b4b5dc8e --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/statistics_writer/README.md @@ -0,0 +1,66 @@ +# TCP to HDF5 statistics writer +The TCP to HDF5 statistics writer can be started with `tcp_hdf5_writer.py` This script imports +`tcp_receiver.py` and `statistics_writer.py`. `tcp_receiver.py` only takes care of receiving packets. +`statistics_writer.py` takes the receive function from the tcp_receiver and uses it to obtain packets. +Any function that can deliver statistics packets can be used by this code. +`statistics_writer.py` takes care of processing the packets it receives, filling statistics matrices +and writing those matrices (as well as a bunch of metadata) to hdf5. + + +### TCP Statistics writer + +The TCP statistics writer can be called with the `tcp_hdf5_writer.py` script. +This script can be called with the following arguments: + ``` + -a --host the address to connect to + -p --port the port to use + -f --file file to read from (as opposed to host and port) + -i --interval The time between creating new files in hours + -o --output_dir specifies the folder to write all the files + -m --mode sets the statistics type to be decoded options: "SST", "XST", "BST" + -v --debug takes no arguments, when used prints a lot of extra data to help with debugging + -d --decimation Configure the writer to only store one every n samples. Saves storage space + ``` + +##HFD5 structure +Statistics packets are collected by the StatisticsCollector in to a matrix. Once the matrix is done or a newer +timestamp arrives this matrix along with the header of first packet header, nof_payload_errors and nof_valid_payloads. +The file will be named after the mode it is in and the timestamp of the statistics packets. For example: `SST_1970-01-01-00-00-00.h5`. + + +``` +File +| +|------ {mode_timestamp} |- {statistics matrix} +| |- {first packet header} +| |- {nof_valid_payloads} +| |- {nof_payload_errors} +| +|------ {mode_timestamp} |- {statistics matrix} +| |- {first packet header} +| |- {nof_valid_payloads} +| |- {nof_payload_errors} +| +... +``` + +###reader +There is a statistics reader that is capable of parsing multiple HDF5 statistics files in to +a more easily usable format. It also allows for filtering between certain timestamps. +`statistics_reader.py` takes the following arguments: +`--files list of files to parse` +`--end_time highest timestamp to process in isoformat` +`--start_time lowest timestamp to process in isoformat` + +ex: `python3 statistics_reader.py --files SST_2021-10-04-07-36-52.h5 --end_time 2021-10-04#07:50:08.937+00:00` +This will parse all the statistics in the file `SST_2021-10-04-07-36-52.h5` up to the timestamp `2021-10-04#07:50:08.937+00:00` + +This file can be used as both a testing tool and an example for dealing with HDF5 statistics. +The code serves can serve as a starting point for further development. To help with these purposes a bunch of simple +helper functions are provided. + +###test server +There is a test server that will continuously send out the same statistics packet. +Its called `test_server.py`. Takes `--host`, `--port` and `--file` as optional input arguments. +Defaults to address `'127.0.0.1'`, port `65433` and file `devices_test_SDP_SST_statistics_packets.bin` + diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/SST_2021-10-04-07-36-52.h5 b/tangostationcontrol/tangostationcontrol/statistics_writer/SST_2021-10-04-07-36-52.h5 new file mode 100644 index 0000000000000000000000000000000000000000..26179fc59a2fb032bb35d779676befd4ebe26356 Binary files /dev/null and b/tangostationcontrol/tangostationcontrol/statistics_writer/SST_2021-10-04-07-36-52.h5 differ diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/__init__.py b/tangostationcontrol/tangostationcontrol/statistics_writer/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/hdf5_writer.py b/tangostationcontrol/tangostationcontrol/statistics_writer/hdf5_writer.py new file mode 100644 index 0000000000000000000000000000000000000000..eb7fa643bd9c8d74b1e946f468532c4852ecaba5 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/statistics_writer/hdf5_writer.py @@ -0,0 +1,242 @@ +# imports for working with datetime objects +from datetime import datetime, timedelta +import pytz + +# python hdf5 +import h5py + +import numpy +import logging + +# import statistics classes with workaround +import sys +sys.path.append("..") +from tangostationcontrol.devices.sdp.statistics_packet import SSTPacket, XSTPacket, BSTPacket, StatisticsPacket +import tangostationcontrol.devices.sdp.statistics_collector as statistics_collector + + +logger = logging.getLogger("statistics_writer") + +__all__ = ["hdf5_writer"] + +class hdf5_writer: + + SST_MODE = "SST" + XST_MODE = "XST" + BST_MODE = "BST" + + def __init__(self, new_file_time_interval, file_location, statistics_mode, decimation_factor): + + # all variables that deal with the matrix that's currently being decoded + self.current_matrix = None + self.current_timestamp = datetime.min.replace(tzinfo=pytz.UTC) + + # counter that tracks how many statistics have been received + self.statistics_counter = 0 + + # the header of the first packet of a new matrix is written as metadata. + # Assumes all subsequent headers of the same matrix are identical (minus index) + self.statistics_header = None + + # file handing + self.file_location = file_location + self.decimation_factor = decimation_factor + self.new_file_time_interval = timedelta(seconds=new_file_time_interval) + self.last_file_time = datetime.min.replace(tzinfo=pytz.UTC) + self.file = None + + # parameters that are configured depending on the mode the statistics writer is in (SST,XST,BST) + self.decoder = None + self.collector = None + self.store_function = None + self.mode = statistics_mode.upper() + self.config_mode() + + def next_packet(self, packet): + """ + All statistics packets come with a timestamp of the time they were measured. All the values will be spread across multiple packets. + As long as the timestamp is the same they belong in the same matrix. This code handles collecting the matrix from those multiple + packets as well as storing matrices and starting new ones + + The code receives new packets and checks the statistics timestamp of them. If the timestamp is higher than the current timestamp + it will close the current matrix, store it and start a new one. + """ + + # process the packet + statistics_packet = self.decoder(packet) + + if not self.statistics_header: + self.statistics_header = statistics_packet.header() + + # grab the timestamp + statistics_timestamp = statistics_packet.timestamp() + + # ignore packets with no timestamp, as they indicate FPGA processing was disabled + # and are useless anyway. + if statistics_packet.block_serial_number == 0: + logger.warning(f"Received statistics with no timestamp. Packet dropped.") + return + + # check if te statistics timestamp is unexpectedly older than the current one + if statistics_timestamp < self.current_timestamp: + logger.warning(f"Received statistics with earlier timestamp than is currently being processed ({statistics_timestamp}). Packet dropped.") + return + + # if this statistics packet has a new timestamp it means we need to start a new matrix + if statistics_timestamp > self.current_timestamp: + self.start_new_matrix(statistics_timestamp) + self.current_timestamp = statistics_timestamp + + self.process_packet(packet) + + def start_new_matrix(self, timestamp): + """ + is called when a statistics packet with a newer timestamp is received. + Writes the matrix to the hdf5 file + Creates a new hdf5 file if needed + updates current timestamp and statistics matrix collector + """ + + # only write the specified fraction of statistics, skip the rest + if self.statistics_counter % self.decimation_factor != 0: + logger.debug(f"Skipping statistic with timestamp: {timestamp}. Only writing 1/{self.decimation_factor} statistics") + + # increment even though its skipped + self.statistics_counter += 1 + return + + # received new statistic, so increment counter + self.statistics_counter += 1 + + logger.debug(f"starting new matrix with timestamp: {timestamp}") + + # write the finished (and checks if its the first matrix) + if self.current_matrix is not None: + try: + self.write_matrix() + except Exception as e: + time = self.current_timestamp.strftime("%Y-%m-%d-%H-%M-%S-%f")[:-3] + logger.exception(f"Exception while attempting to write matrix to HDF5. Matrix: {time} dropped") + + # only start a new file if its time AND we are done with the previous matrix. + if timestamp >= self.new_file_time_interval + self.last_file_time: + self.start_new_hdf5(timestamp) + + # create a new and empty current_matrix + self.current_matrix = self.collector() + self.statistics_header = None + + def write_matrix(self): + logger.debug("writing matrix to file") + """ + Writes the finished matrix to the hdf5 file + """ + + # create the new hdf5 group based on the timestamp of packets + current_group = self.file.create_group("{}_{}".format(self.mode, self.current_timestamp.isoformat(timespec="milliseconds"))) + + # store the statistics values for the current group + self.store_function(current_group) + + # might be optional, but they're easy to add. + current_group.create_dataset(name="nof_payload_errors", data=self.current_matrix.parameters["nof_payload_errors"]) + current_group.create_dataset(name="nof_valid_payloads", data=self.current_matrix.parameters["nof_valid_payloads"]) + + # get the statistics header + header = self.statistics_header + + # can't store datetime objects, convert to string instead + header["timestamp"] = header["timestamp"].isoformat(timespec="milliseconds") + + # Stores the header of the packet received for this matrix as a list of atttributes + for k,v in header.items(): + if type(v) == dict: + for subk, subv in v.items(): + current_group.attrs[f"{k}_{subk}"] = subv + else: + current_group.attrs[k] = v + + def write_sst_matrix(self, current_group): + # store the SST values + current_group.create_dataset(name="values", data=self.current_matrix.parameters["sst_values"].astype(numpy.float32), compression="gzip") + + def write_xst_matrix(self, current_group): + # requires a function call to transform the xst_blocks in to the right structure + current_group.create_dataset(name="values", data=self.current_matrix.xst_values().astype(numpy.cfloat), compression="gzip") + + def write_bst_matrix(self, current_group): + raise NotImplementedError("BST values not implemented") + + + def process_packet(self, packet): + """ + Adds the newly received statistics packet to the statistics matrix + """ + # only process the packets of the wanted fraction + if self.statistics_counter % self.decimation_factor != 0: + return + + self.current_matrix.process_packet(packet) + + def start_new_hdf5(self, timestamp): + + if self.file is not None: + try: + self.file.close() + except Exception as e: + logger.exception(f"Error while attempting to close hdf5 file to disk. file {self.file} likely empty, please verify integrity.") + + current_time = str(timestamp.strftime("%Y-%m-%d-%H-%M-%S")) + logger.info(f"creating new file: {self.file_location}/{self.mode}_{current_time}.h5") + + try: + self.file = h5py.File(f"{self.file_location}/{self.mode}_{current_time}.h5", 'w') + except Exception as e: + logger.exception(f"Error while creating new file") + raise e + + self.last_file_time = timestamp + + def config_mode(self): + logger.debug(f"attempting to configure {self.mode} mode") + + """ + Configures the object for the correct statistics type to be used. + decoder: the class to decode a single packet + collector: the class to collect statistics packets + store_function: the function to write the mode specific data to file + """ + + if self.mode == self.SST_MODE: + self.decoder = SSTPacket + self.collector = statistics_collector.SSTCollector + self.store_function = self.write_sst_matrix + + elif self.mode == self.XST_MODE: + self.decoder = XSTPacket + self.collector = statistics_collector.XSTCollector + self.store_function = self.write_xst_matrix + + elif self.mode == self.BST_MODE: + self.store_function = self.write_bst_matrix + raise NotImplementedError("BST collector has not yet been implemented") + + else: + raise ValueError("invalid statistics mode specified '{}', please use 'SST', 'XST' or 'BST' ".format(self.mode)) + + def close_writer(self): + """ + Function that can be used to stop the writer without data loss. + """ + logger.debug("closing hdf5 file") + if self.file is not None: + if self.current_matrix is not None: + # Write matrix if one exists + # only creates file if there is a matrix to actually write + try: + self.write_matrix() + finally: + filename = str(self.file) + self.file.close() + logger.debug(f"{filename} closed") + logger.debug(f"Received a total of {self.statistics_counter} statistics while running. With {int(self.statistics_counter/self.decimation_factor)} written to disk ") diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/receiver.py b/tangostationcontrol/tangostationcontrol/statistics_writer/receiver.py new file mode 100644 index 0000000000000000000000000000000000000000..cd6c0af4cd5d4a1f8cdc3c2e37d86f6bd655db53 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/statistics_writer/receiver.py @@ -0,0 +1,74 @@ +import socket + +import sys +sys.path.append("..") +from tangostationcontrol.devices.sdp.statistics_packet import StatisticsPacket +import os + +class receiver: + """ Reads data from a file descriptor. """ + + HEADER_LENGTH = 32 + + def __init__(self, fd): + self.fd = fd + + def get_packet(self) -> bytes: + """ Read exactly one statistics packet from the TCP connection. """ + + # read only the header, to compute the size of the packet + header = self.read_data(self.HEADER_LENGTH) + packet = StatisticsPacket(header) + + # read the rest of the packet (payload) + payload_length = packet.expected_size() - len(header) + payload = self.read_data(payload_length) + + # add payload to the header, and return the full packet + return header + payload + + def read_data(self, data_length: int) -> bytes: + """ Read exactly data_length bytes from the TCP connection. """ + + data = b'' + while len(data) < data_length: + # try to read the remainder. + # NOTE: recv() may return less data than requested, and returns 0 + # if there is nothing left to read (end of stream) + more_data = os.read(self.fd, data_length - len(data)) + if not more_data: + # connection got dropped + raise EOFError("End of stream") + + data += more_data + + return data + +class tcp_receiver(receiver): + def __init__(self, HOST, PORT): + self.host = HOST + self.port = PORT + + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect((self.host, self.port)) + + super().__init__(fd=self.sock.fileno()) + + def reconnect(self): + self.fd = None + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect((self.host, self.port)) + self.fd = self.sock.fileno() + return True + + + +class file_receiver(receiver): + def __init__(self, filename): + self.filename = filename + self.fileno = os.open(filename, os.O_RDONLY) + + super().__init__(fd=self.fileno) + + def __del__(self): + os.close(self.fileno) diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/statistics_reader.py b/tangostationcontrol/tangostationcontrol/statistics_writer/statistics_reader.py new file mode 100644 index 0000000000000000000000000000000000000000..67ff6377cdf90326d7a9fa33a015c1ac5861064d --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/statistics_writer/statistics_reader.py @@ -0,0 +1,252 @@ +import h5py +import numpy +import datetime +import argparse +import os +import psutil +import pytz + +process = psutil.Process(os.getpid()) + +import logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger("hdf5_explorer") +logger.setLevel(logging.DEBUG) + + +def timeit(method): + """ + Simple decorator function to log time, function and process memory usage + """ + + def timed(*args, **kw): + global RESULT + s = datetime.datetime.now() + RESULT = method(*args, **kw) + e = datetime.datetime.now() + + sizeMb = process.memory_info().rss / 1024 / 1024 + sizeMbStr = "{0:,}".format(round(sizeMb, 2)) + + logger.debug('Time taken = %s, %s ,size = %s MB' % (e - s, method.__name__, sizeMbStr)) + return RESULT + return timed + + +class statistics_parser: + """ + This class goes through the file and creates a list of all statistics in the file it is given + """ + + def __init__(self): + + # list of all statistics + self.statistics = [] + + # dict of all statistics, allows for easier access. + self.statistics_dict = {} + + # for setting the range of times to parse. Initialise with the build in minimum and maximum values + self.start_time = datetime.datetime.min.replace(tzinfo=pytz.UTC) + self.end_time = datetime.datetime.max.replace(tzinfo=pytz.UTC) + + def set_start_time(self, start_time): + """ + set the lowest statistics timestamp to store + """ + self.start_time = datetime.datetime.fromisoformat(start_time) + + def set_end_time(self, end_time): + """ + set the highest statistics timestamp to store + """ + self.end_time = datetime.datetime.fromisoformat(end_time) + + @timeit + def parse_file(self, files): + """ + This function opens and parses the statistics HDF5 file and adds it to self.statistics. + """ + + # if its just a single file the type could be string + if type(files) is str: + files = [files] + + for file in files: + hdf5_file = h5py.File(file, 'r') + + # go through all the groups + logger.debug(f"Parsing hdf5 statistics file") + + for group_key in hdf5_file.keys(): + try: + # first get the statistic + statistic = statistics_data(hdf5_file, group_key) + + # extract the timestamp and convert to datetime + statistic_time = statistic.timestamp + + # check if the timestamp is before the start time + if statistic_time < self.start_time: + continue + + # check if the timestamp is after the end times + if statistic_time > self.end_time: + # Exit, we're done + logger.debug(f"Parsed {len(self.statistics)} statistics") + return + + # append to the statistics list + self.statistics.append(statistic) + self.statistics_dict[statistic.timestamp.isoformat(timespec="milliseconds")] = statistic + + except: + logger.warning(f"Encountered an error while parsing statistic. Skipped: {group_key}") + + logger.debug(f"Parsed {len(self.statistics)} statistics") + + @timeit + def collect_values(self): + """" + Collects all of the statistics values in to a single giant numpy array + Uses a lot more memory (Basically double since the values make up the bulk of memory) + """ + lst = [i.values for i in self.statistics] + value_array = numpy.stack(lst) + return value_array + + def sort_by_timestamp(self): + """ + Ensures the statistics are correctly sorted. + In case files arent given in sequential order. + """ + self.statistics.sort(key=lambda r: r.timestamp) + + def get_statistic(self, timestamp): + """ + Returns a statistic object based on the timestamp given. + """ + for i in self.statistics: + if i.timestamp == datetime.datetime.fromisoformat(timestamp): + return i + + raise ValueError(f"No statistic with timestamp {timestamp} found, make sure to use the isoformat") + + def list_statistics(self): + """ + Returns a list of all statistics + """ + return self.statistics_dict.keys() + + def get_statistics_count(self): + """ + Simply returns the amount of statistics + """ + return len(self.statistics) + + +class statistics_data: + """ + This class takes the file and the statistics name as its __init__ arguments and then stores the + the datasets in them. + """ + + # we will be creating potentially tens of thousands of these object. Using __slots__ makes them faster and uses less memory. At the cost of + # having to list all self attributes here. + __slots__ = ("version_id", "timestamp", "station_id", "source_info_t_adc", "source_info_subband_calibrated_flag", "source_info_payload_error", + "source_info_payload_error", "source_info_payload_error", "source_info_nyquist_zone_index", "source_info_gn_index", + "source_info_fsub_type", "source_info_beam_repositioning_flag", "source_info_antenna_band_index", "source_info__raw", + "observation_id", "nof_statistics_per_packet", "nof_signal_inputs", "nof_bytes_per_statistic", "marker", "integration_interval_raw", + "integration_interval", "data_id__raw", "block_serial_number", "block_period_raw", "block_period", "data_id_signal_input_index", + "data_id_subband_index", "data_id_first_baseline", "data_id_beamlet_index", "nof_valid_payloads", "nof_payload_errors", "values", ) + + + def __init__(self, file, group_key): + + # get all the general header info + self.version_id = file[group_key].attrs["version_id"] + self.station_id = file[group_key].attrs["station_id"] + + # convert string timestamp to datetime object + self.timestamp = datetime.datetime.fromisoformat(file[group_key].attrs["timestamp"]) + + self.source_info_t_adc = file[group_key].attrs["source_info_t_adc"] + self.source_info_subband_calibrated_flag = file[group_key].attrs["source_info_subband_calibrated_flag"] + self.source_info_payload_error = file[group_key].attrs["source_info_payload_error"] + self.source_info_nyquist_zone_index = file[group_key].attrs["source_info_payload_error"] + self.source_info_gn_index = file[group_key].attrs["source_info_gn_index"] + self.source_info_fsub_type = file[group_key].attrs["source_info_fsub_type"] + self.source_info_beam_repositioning_flag = file[group_key].attrs["source_info_beam_repositioning_flag"] + self.source_info_antenna_band_index = file[group_key].attrs["source_info_antenna_band_index"] + self.source_info__raw = file[group_key].attrs["source_info__raw"] + + self.observation_id = file[group_key].attrs["observation_id"] + self.nof_statistics_per_packet = file[group_key].attrs["nof_statistics_per_packet"] + self.nof_signal_inputs = file[group_key].attrs["nof_signal_inputs"] + self.nof_bytes_per_statistic = file[group_key].attrs["nof_bytes_per_statistic"] + self.marker = file[group_key].attrs["marker"] + self.integration_interval_raw = file[group_key].attrs["integration_interval_raw"] + self.integration_interval = file[group_key].attrs["integration_interval"] + self.data_id__raw = file[group_key].attrs["data_id__raw"] + + self.block_serial_number = file[group_key].attrs["block_serial_number"] + self.block_period_raw = file[group_key].attrs["block_period_raw"] + self.block_period = file[group_key].attrs["block_period"] + + # get SST specific stuff + if self.marker == "S": + self.data_id_signal_input_index = file[group_key].attrs["data_id_signal_input_index"] + + # get XST specific stuff + if self.marker == "X": + self.data_id_subband_index = file[group_key].attrs["data_id_subband_index"] + self.data_id_first_baseline = file[group_key].attrs["data_id_first_baseline"] + + # get BST specific stuff + if self.marker == "B": + self.data_id_beamlet_index = file[group_key].attrs["data_id_beamlet_index"] + + # get the datasets + self.nof_valid_payloads = numpy.array(file.get(f"{group_key}/nof_valid_payloads")) + self.nof_payload_errors = numpy.array(file.get(f"{group_key}/nof_payload_errors")) + self.values = numpy.array(file.get(f"{group_key}/values")) + + +def main(): + parser = argparse.ArgumentParser(description='Select a file to explore') + parser.add_argument( + '--files', type=str, nargs="+", required=True, + help='the name and path of the files, takes one or more files') + parser.add_argument( + '--start_time', type=str, required=True, + help='lowest timestamp to process (uses isoformat, ex: 2021-10-04T07:50' + ':08.937+00:00)') + parser.add_argument( + '--end_time', type=str, required=True, + help='highest timestamp to process (usesisoformat, ex: 2021-10-04T07:50' + ':08.937+00:00)') + + args = parser.parse_args() + files = args.files + end_time = args.end_time + start_time = args.start_time + + # create the parser + parser = statistics_parser() + + # set the correct time ranges + if end_time is not None: + parser.set_end_time(end_time) + if start_time is not None: + parser.set_start_time(start_time) + + # parse all the files + parser.parse_file(files) + + # for good measure sort all the statistics by timestamp. Useful when multiple files are given out of order + parser.sort_by_timestamp() + + # get a single numpy array of all the statistics stored. + array = parser.collect_values() + + logger.debug(f"Collected the statistics values of {parser.get_statistics_count()} statistics in to one gaint array of shape: {array.shape} and type: {array.dtype}") diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/statistics_writer.py b/tangostationcontrol/tangostationcontrol/statistics_writer/statistics_writer.py new file mode 100644 index 0000000000000000000000000000000000000000..1a1ecb671159e1b3ca143ecbf860000d6cdbe0c5 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/statistics_writer/statistics_writer.py @@ -0,0 +1,109 @@ +import argparse +import time +import sys + +from tangostationcontrol.statistics_writer.receiver import tcp_receiver, file_receiver +from tangostationcontrol.statistics_writer.hdf5_writer import hdf5_writer + +import logging +logging.basicConfig(level=logging.INFO, format = '%(asctime)s:%(levelname)s: %(message)s') +logger = logging.getLogger("statistics_writer") + +def main(): + parser = argparse.ArgumentParser( + description='Converts a stream of statistics packets into HDF5 files.') + parser.add_argument( + '-a', '--host', type=str, required=True, help='the host to connect to') + parser.add_argument( + '-p', '--port', type=int, default=0, + help='the port to connect to, or 0 to use default port for the ' + 'selected mode (default: %(default)s)') + parser.add_argument( + '-f', '--file', type=str, required=True, help='the file to read from') + parser.add_argument( + '-m', '--mode', type=str, choices=['SST', 'XST', 'BST'], default='SST', + help='sets the statistics type to be decoded options (default: ' + '%(default)s)') + parser.add_argument( + '-i', '--interval', type=float, default=3600, nargs="?", + help='The time between creating new files in seconds (default: ' + '%(default)s)') + parser.add_argument( + '-o', '--output_dir', type=str, default=".", nargs="?", + help='specifies the folder to write all the files (default: ' + '%(default)s)') + parser.add_argument( + '-v', '--debug', dest='debug', action='store_true', default=False, + help='increase log output') + parser.add_argument( + '-d', '--decimation', type=int, default=1, + help='Configure the writer to only store one every n samples. Saves ' + 'storage space') + parser.add_argument( + '-r', '--reconnect', dest='reconnect', action='store_true', default=False, + help='Set the writer to keep trying to reconnect whenever connection ' + 'is lost. (default: %(default)s)') + + args = parser.parse_args() + + # argparse arguments + host = args.host + port = args.port + filename = args.file + output_dir = args.output_dir + interval = args.interval + mode = args.mode + decimation = args.decimation + debug = args.debug + reconnect = args.reconnect + + if decimation < 1: + raise ValueError("Please use an integer --Decimation value 1 or higher to only store one every n statistics' ") + + if port == 0: + default_ports = { "SST": 5101, "XST": 5102, "BST": 5103 } + port = default_ports[mode] + + if debug: + logger.setLevel(logging.DEBUG) + logger.debug("Setting loglevel to DEBUG") + + # creates the TCP receiver that is given to the writer + if filename: + receiver = file_receiver(filename) + elif host and port: + receiver = tcp_receiver(host, port) + else: + logger.fatal("Must provide either a host and port, or a file to receive input from") + sys.exit(1) + + # create the writer + writer = hdf5_writer(new_file_time_interval=interval, file_location=output_dir, statistics_mode=mode, decimation_factor=decimation) + + # start looping + try: + while True: + try: + packet = receiver.get_packet() + writer.next_packet(packet) + except EOFError: + if reconnect and not filename: + logger.warning("Connection lost, attempting to reconnect") + while True: + try: + receiver.reconnect() + except Exception as e: + logger.warning(f"Could not reconnect: {e.__class__.__name__}: {e}") + time.sleep(10) + else: + break + logger.warning("Reconnected! Resuming operations") + else: + logger.info("End of input.") + raise SystemExit + + except KeyboardInterrupt: + # user abort, don't complain + logger.warning("Received keyboard interrupt. Stopping.") + finally: + writer.close_writer() diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_1.h5 b/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_1.h5 new file mode 100644 index 0000000000000000000000000000000000000000..2d04a526e1ef73d7bd636e3b564192d95e49cef5 Binary files /dev/null and b/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_1.h5 differ diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_2.h5 b/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_2.h5 new file mode 100644 index 0000000000000000000000000000000000000000..45fd32d831508f8d632c6f1778d4d9bb73059294 Binary files /dev/null and b/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_2.h5 differ diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_3.h5 b/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_3.h5 new file mode 100644 index 0000000000000000000000000000000000000000..5c971e8e2cea131d6c9ba8b7e6b1d645f205f276 Binary files /dev/null and b/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_3.h5 differ diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/test/__init__.py b/tangostationcontrol/tangostationcontrol/statistics_writer/test/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/test/devices_test_SDP_SST_statistics_packets.bin b/tangostationcontrol/tangostationcontrol/statistics_writer/test/devices_test_SDP_SST_statistics_packets.bin new file mode 100644 index 0000000000000000000000000000000000000000..e94347b86a0a03b940eb84980ec8f6d3b6d4e2d7 Binary files /dev/null and b/tangostationcontrol/tangostationcontrol/statistics_writer/test/devices_test_SDP_SST_statistics_packets.bin differ diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/test/test_server.py b/tangostationcontrol/tangostationcontrol/statistics_writer/test/test_server.py new file mode 100644 index 0000000000000000000000000000000000000000..74101b93de2e83824c70e4630e8560ae24b28fa8 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/statistics_writer/test/test_server.py @@ -0,0 +1,55 @@ +import socket +import time + +import argparse + +import logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger("statistics_test_server") +logger.setLevel(logging.DEBUG) + +parser = argparse.ArgumentParser(description='Select what hostname to use and what port to use') +parser.add_argument('--port', type=int, help='port to use', default=65433) +parser.add_argument('--host', help='host to use', default='127.0.0.1') +parser.add_argument('--file', help='file to use as data', default='devices_test_SDP_SST_statistics_packets.bin') +parser.add_argument('--interval', type=int, help='ime between sending entire files content', default=1) + +args = parser.parse_args() +HOST = args.host +PORT = args.port +FILE = args.file +INTERVAL = args.interval + + +while True: + try: + f = open(FILE, "rb") + data = f.read() + except Exception as e: + logger.error(f"File not found, are you sure: '{FILE}' is a valid path, Exception: {e}") + exit() + + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + logger.debug(f"Starting TCP test server on {HOST} {PORT}") + logger.debug("To interrupt the script, press Ctrl-C twice within a second") + + s.bind((HOST, PORT)) + s.listen() + conn, addr = s.accept() + + with conn: + logger.debug(f'Connected by: {addr}') + + while True: + time.sleep(INTERVAL) + conn.sendall(data) + + except KeyboardInterrupt: + logger.info("Received keyboard interrupt. Stopping.") + exit() + except Exception as e: + logger.warning(f"Exception occurred: {e}") + + # just do 2 interrupt within a second to quit the program + time.sleep(1) diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/__init__.py b/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_client.py b/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_client.py new file mode 100644 index 0000000000000000000000000000000000000000..cef6a079d17dc0fb45d71f181ee2be908e9bd091 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_client.py @@ -0,0 +1,62 @@ +import socket +import sys +import netifaces as ni +from datetime import datetime +import time + +class UDP_Client: + + def __init__(self, server_ip:str, server_port:int): + self.server_ip = server_ip + self.server_port = server_port + self.server_data = None + self.server_addr = None # tuple of address info + + def run(self): + # Create socket for server + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) + print("Do Ctrl+c to exit the program !!") + print('\n\n*** This Client keeps sending the same SST packet with an interval of 1s ***') + + # Let's send data through UDP protocol + while True: + + #Old interactive interface + #send_data = input("Type some text to send =>"); + #s.sendto(send_data.encode('utf-8'), (self.server_ip, self.server_port)) + #print("\n\n 1. Client Sent : ", send_data, "\n\n") + #self.server_data, self.server_addr = s.recvfrom(4096) + #print("\n\n 2. Client received : ", self.server_data.decode('utf-8'), "\n\n") + + time.sleep(1) + + f = open("../../test/SDP_SST_statistics_packet.bin", "rb") + send_data = f.read() + s.sendto(send_data, (self.server_ip, self.server_port)) + print("\n\n 1. Client Sent SST Packet at: ", datetime.now()) + self.server_data, self.server_addr = s.recvfrom(4096) + print("\n\n 2. Client received : ", self.server_data.decode('utf-8'), "\n\n") + + # close the socket + s.close() + +if __name__ == '__main__': + + if len(sys.argv) == 3: + if sys.argv[1]=='localhost': + server_ip = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr'] + else : + server_ip = sys.argv[1] + server_port = int(sys.argv[2]) + #local_ip = local_ip = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr'] + #server_ip = local_ip + else: + print("Run like : python3 udp_client.py <server_ip> <server_port>") + exit(1) + + client = UDP_Client(server_ip,server_port) + client.run() + + + + \ No newline at end of file diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_server.py b/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_server.py new file mode 100644 index 0000000000000000000000000000000000000000..45624761519287b13bbce5c73cf8d8cb7dff9201 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_server.py @@ -0,0 +1,50 @@ +import socket +import sys +import time +import netifaces as ni +from datetime import datetime + +class UDP_Server: + + def __init__(self, ip:str, port:int, buffer_size:int = 8192): + self.ip = ip + self.port = port + self.buffer_size = buffer_size + self.recv_data = None + self.recv_addr = None + + def run(self): + # Create a UDP socket + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + # Bind the socket to the port + server_address = (self.ip, self.port) + s.bind(server_address) + print("Do Ctrl+c to exit the program !!") + print("\n\n####### Server is listening on %s - port %s #######" % (self.ip,self.port)) + + while True: + + self.recv_data, self.recv_addr = s.recvfrom(self.buffer_size) + print("\n\n 2. Server received at: ", datetime.now(), "\n\n") + + '''Server response''' + #send_data = input("Type some text to send => ") + send_data = 'Packet received. Waiting for the next one.' + s.sendto(send_data.encode('utf-8'), self.recv_addr) + print("\n\n 1. Server sent : ", send_data,"\n\n") + + #time.sleep(10) + #s.close() + + break + + # close the socket + s.close() + + def get_recv_data(self): + return self.recv_data + +if __name__ == '__main__': + local_ip = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr'] + server = UDP_Server(local_ip,5600) + server.run() diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_write_manager.py b/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_write_manager.py new file mode 100644 index 0000000000000000000000000000000000000000..0c11f6a82dc11f8151eb771b90033feb38ef9c42 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_write_manager.py @@ -0,0 +1,81 @@ +from datetime import datetime +import time +import os +import h5py +import numpy as np +from statistics_writer.udp_dev import udp_server as udp +import netifaces as ni +from statistics_packet import SSTPacket + +__all__ = ["statistics_writer"] + + +class Statistics_Writer: + + def __init__(self, new_file_time_interval): + + self.new_file_time_interval = new_file_time_interval + self.packet_cnt = 0 + + # Define ip and port of the receiver + self.local_ip = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr'] + self.server = udp.UDP_Server(self.local_ip, 5600) + + # Create data directory if not exists + try: + os.makedirs('../data') + except: + print('Data directory already created') + + # create initial file + self.last_file_time = time.time() + self.file = None + self.new_hdf5() + + def write_packet(self, raw_data): + # create new file if the file was created more than the allowed time ago + if time.time() >= self.new_file_time_interval + self.last_file_time: + self.new_hdf5() + + self.packet_cnt += 1 + + # create dataset with the raw data in it + self.write_raw(raw_data) + self.write_metadata(raw_data) + + def new_hdf5(self): + + if self.file is not None: + self.file.close() + + timestamp = datetime.now() + current_time = str(timestamp.strftime("%Y-%m-%d-%H-%M-%S")) + print("creating new file: data/{}.h5".format(current_time)) + self.file = h5py.File("data/{}.h5".format(current_time), 'w') + self.last_file_time = time.time() + + def write_metadata(self, packet): + # decode packet + self.sst = SSTPacket(packet) + header = self.sst.header() + header_bytes = bytes(str(header), "utf-8") + header_bytes = np.frombuffer(header_bytes, dtype=np.uint8) + self.file.create_dataset('packet_{}_header'.format(self.packet_cnt), data=header_bytes) + + def write_raw(self, packet): + # create dataset with the raw data in it + data = np.frombuffer(packet, dtype=np.uint8) + self.file.create_dataset('packet_{}_raw'.format(self.packet_cnt), data=data) + + +if __name__ == "__main__": + # create a data dumper that creates a new file every 10s (for testing) + test = Statistics_Writer(new_file_time_interval=10) + + # simple loop to write data every second + while True: + test.server.run() + data = test.server.get_recv_data() + test.write_packet(data) + + # time.sleep(1) diff --git a/devices/test/README.md b/tangostationcontrol/tangostationcontrol/test/README.md similarity index 99% rename from devices/test/README.md rename to tangostationcontrol/tangostationcontrol/test/README.md index 4007ef0a30b75869e04b0e69745947f73d4520ba..050b1de05d9206151661aabd11a77358ab464576 100644 --- a/devices/test/README.md +++ b/tangostationcontrol/tangostationcontrol/test/README.md @@ -120,7 +120,7 @@ Docker container. A simple interactive Docker exec is enough to access them: ```sh docker exec -it device-sdp /bin/bash -cd /opt/lofar2.0/tango/devices/ +cd /opt/lofar/tango/devices/ tox ``` diff --git a/devices/test/SDP_SST_statistics_packet.bin b/tangostationcontrol/tangostationcontrol/test/SDP_SST_statistics_packet.bin similarity index 99% rename from devices/test/SDP_SST_statistics_packet.bin rename to tangostationcontrol/tangostationcontrol/test/SDP_SST_statistics_packet.bin index ade2d62c32eb6cbf4fb9b5ec2d7c0368ab0af408..a45b77587a8104cbeb756d85cbb757f02abf39bf 100644 Binary files a/devices/test/SDP_SST_statistics_packet.bin and b/tangostationcontrol/tangostationcontrol/test/SDP_SST_statistics_packet.bin differ diff --git a/tangostationcontrol/tangostationcontrol/test/SDP_SST_statistics_packets.bin b/tangostationcontrol/tangostationcontrol/test/SDP_SST_statistics_packets.bin new file mode 100644 index 0000000000000000000000000000000000000000..e94347b86a0a03b940eb84980ec8f6d3b6d4e2d7 Binary files /dev/null and b/tangostationcontrol/tangostationcontrol/test/SDP_SST_statistics_packets.bin differ diff --git a/tangostationcontrol/tangostationcontrol/test/SDP_XST_statistics_packets.bin b/tangostationcontrol/tangostationcontrol/test/SDP_XST_statistics_packets.bin new file mode 100644 index 0000000000000000000000000000000000000000..97c08e3bfb47bf56c30288b5e62cc60c7034b417 Binary files /dev/null and b/tangostationcontrol/tangostationcontrol/test/SDP_XST_statistics_packets.bin differ diff --git a/tangostationcontrol/tangostationcontrol/test/__init__.py b/tangostationcontrol/tangostationcontrol/test/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/devices/test/base.py b/tangostationcontrol/tangostationcontrol/test/base.py similarity index 59% rename from devices/test/base.py rename to tangostationcontrol/tangostationcontrol/test/base.py index 2bcbf59b33b605ba15faa0ad71c0fd53d80274ff..7cf3af7f8becb1f92cde139290394ea540f5d8d6 100644 --- a/devices/test/base.py +++ b/tangostationcontrol/tangostationcontrol/test/base.py @@ -7,8 +7,14 @@ # Distributed under the terms of the APACHE license. # See LICENSE.txt for more info. +from tangostationcontrol.common.lofar_logging import configure_logger + import unittest import testscenarios +import asynctest + +"""Setup logging for unit tests""" +configure_logger(debug=True) class BaseTestCase(testscenarios.WithScenarios, unittest.TestCase): @@ -23,3 +29,10 @@ class TestCase(BaseTestCase): def setUp(self): super().setUp() + + +class AsyncTestCase(testscenarios.WithScenarios, asynctest.TestCase): + """Test case base class for all asyncio unit tests.""" + + def setUp(self): + super().setUp() diff --git a/tangostationcontrol/tangostationcontrol/test/clients/__init__.py b/tangostationcontrol/tangostationcontrol/test/clients/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tangostationcontrol/tangostationcontrol/test/clients/test_attr_wrapper.py b/tangostationcontrol/tangostationcontrol/test/clients/test_attr_wrapper.py new file mode 100644 index 0000000000000000000000000000000000000000..24ba5f506023f4260a35958cba568936cb2ad76f --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/test/clients/test_attr_wrapper.py @@ -0,0 +1,611 @@ +# -*- coding: utf-8 -*- + +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" test Device Server +""" + +# External imports +from tango import DevState + +# Internal imports +from tangostationcontrol.test.clients.test_client import test_client +from tangostationcontrol.clients.attribute_wrapper import * +from tangostationcontrol.devices.lofar_device import * +import tangostationcontrol.devices.lofar_device + +# Test imports +from tango.test_context import DeviceTestContext +from tangostationcontrol.test import base + +import asyncio +import mock + +scalar_dims = (1,) +spectrum_dims = (4,) +image_dims = (3,2) + +str_scalar_val = '1' +str_spectrum_val = ['1','1', '1','1'] +str_image_val = [['1','1'],['1','1'],['1','1']] + + +def dev_init(device): + device.set_state(DevState.INIT) + device.test_client = test_client(device.Fault) + for i in device.attr_list(): + asyncio.run(i.async_set_comm_client(device.test_client)) + device.test_client.start() + + +class TestAttributeTypes(base.TestCase): + def setUp(self): + # Avoid the device trying to access itself as a client + self.deviceproxy_patch = mock.patch.object(tangostationcontrol.devices.lofar_device,'DeviceProxy') + self.deviceproxy_patch.start() + self.addCleanup(self.deviceproxy_patch.stop) + + class str_scalar_device(lofar_device): + scalar_R = attribute_wrapper(comms_annotation="str_scalar_R", datatype=numpy.str) + scalar_RW = attribute_wrapper(comms_annotation="str_scalar_RW", datatype=numpy.str, access=AttrWriteType.READ_WRITE) + + def configure_for_initialise(self): + dev_init(self) + + class bool_scalar_device(lofar_device): + scalar_R = attribute_wrapper(comms_annotation="bool_scalar_R", datatype=numpy.bool_) + scalar_RW = attribute_wrapper(comms_annotation="bool_scalar_RW", datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + + def configure_for_initialise(self): + dev_init(self) + + class float32_scalar_device(lofar_device): + scalar_R = attribute_wrapper(comms_annotation="float32_scalar_R", datatype=numpy.float32) + scalar_RW = attribute_wrapper(comms_annotation="float32_scalar_RW", datatype=numpy.float32, access=AttrWriteType.READ_WRITE) + + def configure_for_initialise(self): + dev_init(self) + + class float64_scalar_device(lofar_device): + scalar_R = attribute_wrapper(comms_annotation="float64_scalar_R", datatype=numpy.float64) + scalar_RW = attribute_wrapper(comms_annotation="float64_scalar_RW", datatype=numpy.float64, access=AttrWriteType.READ_WRITE) + + def configure_for_initialise(self): + dev_init(self) + + class double_scalar_device(lofar_device): + scalar_R = attribute_wrapper(comms_annotation="double_scalar_R", datatype=numpy.double) + scalar_RW = attribute_wrapper(comms_annotation="double_scalar_RW", datatype=numpy.double, access=AttrWriteType.READ_WRITE) + + def configure_for_initialise(self): + dev_init(self) + + class uint8_scalar_device(lofar_device): + scalar_R = attribute_wrapper(comms_annotation="uint8_scalar_R", datatype=numpy.uint8) + scalar_RW = attribute_wrapper(comms_annotation="uint8_scalar_RW", datatype=numpy.uint8, access=AttrWriteType.READ_WRITE) + + def configure_for_initialise(self): + dev_init(self) + + class uint16_scalar_device(lofar_device): + scalar_R = attribute_wrapper(comms_annotation="uint16_scalar_R", datatype=numpy.uint16) + scalar_RW = attribute_wrapper(comms_annotation="uint16_scalar_RW", datatype=numpy.uint16, access=AttrWriteType.READ_WRITE) + + def configure_for_initialise(self): + dev_init(self) + + class uint32_scalar_device(lofar_device): + scalar_R = attribute_wrapper(comms_annotation="uint32_scalar_R", datatype=numpy.uint32) + scalar_RW = attribute_wrapper(comms_annotation="uint32_scalar_RW", datatype=numpy.uint32, access=AttrWriteType.READ_WRITE) + + def configure_for_initialise(self): + dev_init(self) + + class uint64_scalar_device(lofar_device): + scalar_R = attribute_wrapper(comms_annotation="uint64_scalar_R", datatype=numpy.uint64) + scalar_RW = attribute_wrapper(comms_annotation="uint64_scalar_RW", datatype=numpy.uint64, access=AttrWriteType.READ_WRITE) + + def configure_for_initialise(self): + dev_init(self) + + class int16_scalar_device(lofar_device): + scalar_R = attribute_wrapper(comms_annotation="int16_scalar_R", datatype=numpy.int16) + scalar_RW = attribute_wrapper(comms_annotation="int16_scalar_RW", datatype=numpy.int16, access=AttrWriteType.READ_WRITE) + + def configure_for_initialise(self): + dev_init(self) + + class int32_scalar_device(lofar_device): + scalar_R = attribute_wrapper(comms_annotation="int32_scalar_R", datatype=numpy.int32) + scalar_RW = attribute_wrapper(comms_annotation="int32_scalar_RW", datatype=numpy.int32, access=AttrWriteType.READ_WRITE) + + def configure_for_initialise(self): + dev_init(self) + + class int64_scalar_device(lofar_device): + scalar_R = attribute_wrapper(comms_annotation="int64_scalar_R", datatype=numpy.int64) + scalar_RW = attribute_wrapper(comms_annotation="int64_scalar_RW", datatype=numpy.int64, access=AttrWriteType.READ_WRITE) + + def configure_for_initialise(self): + dev_init(self) + + class str_spectrum_device(lofar_device): + spectrum_R = attribute_wrapper(comms_annotation="str_spectrum_R", datatype=numpy.str, dims=spectrum_dims) + spectrum_RW = attribute_wrapper(comms_annotation="str_spectrum_RW", datatype=numpy.str, access=AttrWriteType.READ_WRITE, dims=spectrum_dims) + + def configure_for_initialise(self): + dev_init(self) + + class bool_spectrum_device(lofar_device): + spectrum_R = attribute_wrapper(comms_annotation="bool_spectrum_R", datatype=numpy.bool_, dims=spectrum_dims) + spectrum_RW = attribute_wrapper(comms_annotation="bool_spectrum_RW", datatype=numpy.bool_, access=AttrWriteType.READ_WRITE, dims=spectrum_dims) + + def configure_for_initialise(self): + dev_init(self) + + class float32_spectrum_device(lofar_device): + spectrum_R = attribute_wrapper(comms_annotation="float32_spectrum_R", datatype=numpy.float32, dims=spectrum_dims) + spectrum_RW = attribute_wrapper(comms_annotation="float32_spectrum_RW", datatype=numpy.float32, access=AttrWriteType.READ_WRITE, dims=spectrum_dims) + + def configure_for_initialise(self): + dev_init(self) + + class float64_spectrum_device(lofar_device): + spectrum_R = attribute_wrapper(comms_annotation="float64_spectrum_R", datatype=numpy.float64, dims=spectrum_dims) + spectrum_RW = attribute_wrapper(comms_annotation="float64_spectrum_RW", datatype=numpy.float64, access=AttrWriteType.READ_WRITE, dims=spectrum_dims) + + def configure_for_initialise(self): + dev_init(self) + + class double_spectrum_device(lofar_device): + spectrum_R = attribute_wrapper(comms_annotation="double_spectrum_R", datatype=numpy.double, dims=spectrum_dims) + spectrum_RW = attribute_wrapper(comms_annotation="double_spectrum_RW", datatype=numpy.double, access=AttrWriteType.READ_WRITE, dims=spectrum_dims) + + def configure_for_initialise(self): + dev_init(self) + + class uint8_spectrum_device(lofar_device): + spectrum_R = attribute_wrapper(comms_annotation="uint8_spectrum_R", datatype=numpy.uint8, dims=spectrum_dims) + spectrum_RW = attribute_wrapper(comms_annotation="uint8_spectrum_RW", datatype=numpy.uint8, access=AttrWriteType.READ_WRITE, dims=spectrum_dims) + + def configure_for_initialise(self): + dev_init(self) + + class uint16_spectrum_device(lofar_device): + spectrum_R = attribute_wrapper(comms_annotation="uint16_spectrum_R", datatype=numpy.uint16, dims=spectrum_dims) + spectrum_RW = attribute_wrapper(comms_annotation="uint16_spectrum_RW", datatype=numpy.uint16, access=AttrWriteType.READ_WRITE, dims=spectrum_dims) + + def configure_for_initialise(self): + dev_init(self) + + class uint32_spectrum_device(lofar_device): + spectrum_R = attribute_wrapper(comms_annotation="uint32_spectrum_R", datatype=numpy.uint32, dims=spectrum_dims) + spectrum_RW = attribute_wrapper(comms_annotation="uint32_spectrum_RW", datatype=numpy.uint32, access=AttrWriteType.READ_WRITE, dims=spectrum_dims) + + def configure_for_initialise(self): + dev_init(self) + + class uint64_spectrum_device(lofar_device): + spectrum_R = attribute_wrapper(comms_annotation="uint64_spectrum_R", datatype=numpy.uint64, dims=spectrum_dims) + spectrum_RW = attribute_wrapper(comms_annotation="uint64_spectrum_RW", datatype=numpy.uint64, access=AttrWriteType.READ_WRITE, dims=spectrum_dims) + + def configure_for_initialise(self): + dev_init(self) + + class int16_spectrum_device(lofar_device): + spectrum_R = attribute_wrapper(comms_annotation="int16_spectrum_R", datatype=numpy.int16, dims=spectrum_dims) + spectrum_RW = attribute_wrapper(comms_annotation="int16_spectrum_RW", datatype=numpy.int16, access=AttrWriteType.READ_WRITE, dims=spectrum_dims) + + def configure_for_initialise(self): + dev_init(self) + + class int32_spectrum_device(lofar_device): + spectrum_R = attribute_wrapper(comms_annotation="int32_spectrum_R", datatype=numpy.int32, dims=spectrum_dims) + spectrum_RW = attribute_wrapper(comms_annotation="int32_spectrum_RW", datatype=numpy.int32, access=AttrWriteType.READ_WRITE, dims=spectrum_dims) + + def configure_for_initialise(self): + dev_init(self) + + class int64_spectrum_device(lofar_device): + spectrum_R = attribute_wrapper(comms_annotation="int64_spectrum_R", datatype=numpy.int64, dims=spectrum_dims) + spectrum_RW = attribute_wrapper(comms_annotation="int64_spectrum_RW", datatype=numpy.int64, access=AttrWriteType.READ_WRITE, dims=spectrum_dims) + + def configure_for_initialise(self): + dev_init(self) + + class str_image_device(lofar_device): + image_R = attribute_wrapper(comms_annotation="str_image_R", datatype=numpy.str, dims=(2,3)) + image_RW = attribute_wrapper(comms_annotation="str_image_RW", datatype=numpy.str, access=AttrWriteType.READ_WRITE, dims=(2,3)) + + def configure_for_initialise(self): + dev_init(self) + + class bool_image_device(lofar_device): + image_R = attribute_wrapper(comms_annotation="bool_image_R", datatype=numpy.bool_, dims=(2,3)) + image_RW = attribute_wrapper(comms_annotation="bool_image_RW", datatype=numpy.bool_, access=AttrWriteType.READ_WRITE, dims=(2,3)) + + def configure_for_initialise(self): + dev_init(self) + + class float32_image_device(lofar_device): + image_R = attribute_wrapper(comms_annotation="float32_image_R", datatype=numpy.float32, dims=(2,3)) + image_RW = attribute_wrapper(comms_annotation="float32_image_RW", datatype=numpy.float32, access=AttrWriteType.READ_WRITE, dims=(2,3)) + + def configure_for_initialise(self): + dev_init(self) + + class float64_image_device(lofar_device): + image_R = attribute_wrapper(comms_annotation="float64_image_R", datatype=numpy.float64, dims=(2,3)) + image_RW = attribute_wrapper(comms_annotation="float64_image_RW", datatype=numpy.float64, access=AttrWriteType.READ_WRITE, dims=(2,3)) + + def configure_for_initialise(self): + dev_init(self) + + class double_image_device(lofar_device): + image_R = attribute_wrapper(comms_annotation="double_image_R", datatype=numpy.double, dims=(2,3)) + image_RW = attribute_wrapper(comms_annotation="double_image_RW", datatype=numpy.double, access=AttrWriteType.READ_WRITE, dims=(2,3)) + + def configure_for_initialise(self): + dev_init(self) + + class uint8_image_device(lofar_device): + image_R = attribute_wrapper(comms_annotation="uint8_image_R", datatype=numpy.uint8, dims=(2,3)) + image_RW = attribute_wrapper(comms_annotation="uint8_image_RW", datatype=numpy.uint8, access=AttrWriteType.READ_WRITE, dims=(2,3)) + + def configure_for_initialise(self): + dev_init(self) + + class uint16_image_device(lofar_device): + image_R = attribute_wrapper(comms_annotation="uint16_image_R", datatype=numpy.uint16, dims=(2,3)) + image_RW = attribute_wrapper(comms_annotation="uint16_image_RW", datatype=numpy.uint16, access=AttrWriteType.READ_WRITE, dims=(2,3)) + + def configure_for_initialise(self): + dev_init(self) + + class uint32_image_device(lofar_device): + image_R = attribute_wrapper(comms_annotation="uint32_image_R", datatype=numpy.uint32, dims=(2,3)) + image_RW = attribute_wrapper(comms_annotation="uint32_image_RW", datatype=numpy.uint32, access=AttrWriteType.READ_WRITE, dims=(2,3)) + + def configure_for_initialise(self): + dev_init(self) + + class uint64_image_device(lofar_device): + image_R = attribute_wrapper(comms_annotation="uint64_image_R", datatype=numpy.uint64, dims=(2,3)) + image_RW = attribute_wrapper(comms_annotation="uint64_image_RW", datatype=numpy.uint64, access=AttrWriteType.READ_WRITE, dims=(2,3)) + + def configure_for_initialise(self): + dev_init(self) + + class int16_image_device(lofar_device): + image_R = attribute_wrapper(comms_annotation="int16_image_R", datatype=numpy.int16, dims=(2,3)) + image_RW = attribute_wrapper(comms_annotation="int16_image_RW", datatype=numpy.int16, access=AttrWriteType.READ_WRITE, dims=(2,3)) + + def configure_for_initialise(self): + dev_init(self) + + class int32_image_device(lofar_device): + image_R = attribute_wrapper(comms_annotation="int32_image_R", datatype=numpy.int32, dims=(2,3)) + image_RW = attribute_wrapper(comms_annotation="int32_image_RW", datatype=numpy.int32, access=AttrWriteType.READ_WRITE, dims=(2,3)) + + def configure_for_initialise(self): + dev_init(self) + + class int64_image_device(lofar_device): + image_R = attribute_wrapper(comms_annotation="int64_image_R", datatype=numpy.int64, dims=(2,3)) + image_RW = attribute_wrapper(comms_annotation="int64_image_RW", datatype=numpy.int64, access=AttrWriteType.READ_WRITE, dims=(2,3)) + + def configure_for_initialise(self): + dev_init(self) + + def read_R_test(self, dev, dtype, test_type): + '''Test device''' + with DeviceTestContext(dev, process=True) as proxy: + + #initialise + proxy.initialise() + proxy.on() + + if test_type == "scalar": + expected = numpy.zeros((1,), dtype=dtype) + val = proxy.scalar_RW + elif test_type == "spectrum": + expected = numpy.zeros(spectrum_dims, dtype=dtype) + val = proxy.spectrum_R + elif test_type == "image": + expected = numpy.zeros(image_dims, dtype=dtype) + val = numpy.array(proxy.image_R) #is needed for STR since they act differently + + # cant use all() for 2d arrays so instead compare the dimensions and then flatten to 2d + self.assertEqual(val.shape, expected.shape, " image R array dimensions got mangled. Expected {}, got {}".format(expected.shape, val.shape)) + val.reshape(-1) + else: + self.assertEqual(1,2, " {} is not a valid test_type. please use either scalar, spectrum or image".format(test_type)) + + if test_type == "scalar": + comparison = expected == val + self.assertTrue(comparison, " Value could not be read or was not what was expected. Expected: {}, got {}".format(expected, val)) + else: + comparison = expected == val + equal_arrays = comparison.all() + self.assertTrue(equal_arrays, " Value could not be read or was not what was expected. Expected: {}, got {}".format(expected, val)) + + print(" Test passed! Managed to read R attribute value. got: {}".format(val)) + + def write_RW_test(self, dev, dtype, test_type): + '''Test device''' + with DeviceTestContext(dev, process=True) as proxy: + + #initialise + proxy.initialise() + proxy.on() + + if test_type == "scalar": + + if dtype is numpy.str: + val = str_scalar_val + else: + val = dtype(1) + proxy.scalar_RW = val + elif test_type == "spectrum": + if dtype is numpy.str: + val = str_spectrum_val + else: + val = numpy.full(spectrum_dims, dtype=dtype, fill_value=1) + print(val) + proxy.spectrum_RW = val + elif test_type == "image": + if dtype is numpy.str: + val = str_image_val + else: + val = numpy.full(image_dims, dtype=dtype, fill_value=1) + proxy.image_RW = val + else: + self.assertEqual(1,2, " {} is not a valid test_type. please use either scalar, spectrum or image".format(test_type)) + + # can't really test anything here except that the writing didnt cause an error. + # reading back happens in readback_test + + print(" Test passed! Managed to write: ".format(val)) + + def read_RW_test(self, dev, dtype, test_type): + '''Test device''' + expected = None + val = None + + try: + with DeviceTestContext(dev, process=True) as proxy: + + #initialise + proxy.initialise() + proxy.on() + + if test_type == "scalar": + expected = numpy.zeros((1,), dtype=dtype) + val = proxy.scalar_RW + elif test_type == "spectrum": + expected = numpy.zeros(spectrum_dims, dtype=dtype) + val = proxy.spectrum_RW + elif test_type == "image": + expected = numpy.zeros(image_dims, dtype=dtype) + val = numpy.array(proxy.image_RW) #is needed for STR since they act differently + + # cant use all() for 2d arrays so instead compare the dimensions and then flatten to 2d + self.assertEqual(val.shape, expected.shape, " image R array dimensions got mangled. Expected {}, got {}".format(expected.shape, val.shape)) + val.reshape(-1) + else: + self.assertEqual(1,2, " {} is not a valid test_type. please use either scalar, spectrum or image".format(test_type)) + + if test_type != "scalar": + # spectrums and the now flattened images can be compared with .all() + comparison = expected == val + equal_arrays = comparison.all() + self.assertTrue(equal_arrays, " Value could not be handled by the atrribute_wrappers internal RW storer") + else: + comparison = expected == val + self.assertTrue(comparison, " Value could not be handled by the atrribute_wrappers internal RW storer") + + print(" Test passed! Managed to read internal RW value. got: {}".format(val)) + except Exception as e: + info = "Test failure in {} {} read RW test. Expected: {}, got {}".format(test_type, dtype, expected, val) + raise Exception(info) from e + + def readback_test(self, dev, dtype, test_type): + '''Test device''' + try: + with DeviceTestContext(dev, process=True) as proxy: + + #initialise + proxy.initialise() + proxy.on() + + if test_type == "scalar": + if dtype is numpy.str: + val = str_scalar_val + else: + val = dtype(1) + proxy.scalar_RW = val + result_R = proxy.scalar_R + result_RW = proxy.scalar_RW + elif test_type == "spectrum": + if dtype is numpy.str: + val = str_spectrum_val + else: + val = numpy.full(spectrum_dims, dtype=dtype, fill_value=1) + proxy.spectrum_RW = val + result_R = proxy.spectrum_R + result_RW = proxy.spectrum_RW + elif test_type == "image": + if dtype is numpy.str: + val = str_image_val + else: + val = numpy.full(image_dims, dtype=dtype, fill_value=1) + + # info += " write value: {}".format(val) + proxy.image_RW = val + result_R = proxy.image_R + result_RW = proxy.image_RW + + if dtype != numpy.str: + self.assertEqual(result_R.shape, image_dims, "not the correct dimensions") + + result_R = result_R.reshape(-1) + result_RW = result_RW.reshape(-1) + val = val.reshape(-1) + + else: + # if the test isn't scalar/spectrum or image its wrong + self.assertEqual(1,2, " {} is not a valid test_type. please use either scalar, spectrum or image".format(test_type)) + + if test_type == "scalar": + comparison = result_RW == val + self.assertTrue(comparison, " Value could not be handled by the atrribute_wrappers internal RW storer. attempted to write: {}".format(val)) + comparison = result_R == val + self.assertTrue(comparison, " value in the clients R attribute not equal to what was written. read: {}, wrote {}".format(result_R, val)) + elif dtype != numpy.str: + comparison = result_RW == val + equal_arrays = comparison.all() + self.assertTrue(equal_arrays, " Value could not be handled by the atrribute_wrappers internal RW storer. attempted to write: {}".format(val)) + comparison = result_R == val + equal_arrays = comparison.all() + self.assertTrue(equal_arrays, " value in the clients R attribute not equal to what was written. read: {}, wrote {}".format(result_R, val)) + else: + if test_type == "image": + self.assertEqual(len(result_RW)*len(result_RW[0]), 6, "array dimensions do not match the expected dimensions. expected {}, got: {}".format(val, len(result_RW) * len(result_RW[0]))) + self.assertEqual(len(result_RW) * len(result_RW[0]), 6,"array dimensions do not match the expected dimensions. expected {}, got: {}".format(val, len(result_R) * len([0]))) + else: + self.assertEqual(len(result_RW), 4,"array dimensions do not match the expected dimensions. expected {}, got: {}".format(4, len(result_RW))) + self.assertEqual(len(result_R), 4, "array dimensions do not match the expected dimensions. expected {}, got: {}".format(4, len(result_R))) + + print(" Test passed! Managed write and read back a value: {}".format(val)) + + except Exception as e: + info = "Test failure in {} {} readback test \n\tW: {} \n\tRW: {} \n\tR: {}".format(test_type, dtype, val, result_RW, result_R) + raise Exception(info) from e + + + """ + List of different types to be used with attributes testing, using any other + might have unexpected results. Each type is bound to a device scalar, + spectrum and image class + """ + attribute_type_tests = [ + { + 'type': str, 'scalar': str_scalar_device, + 'spectrum': str_spectrum_device, "image": str_image_device + }, + { + 'type': numpy.bool_, 'scalar': bool_scalar_device, + 'spectrum': bool_spectrum_device, "image": bool_image_device + }, + { + 'type': numpy.float32, 'scalar': float32_scalar_device, + 'spectrum': float32_spectrum_device, "image": float32_image_device + }, + { + 'type': numpy.float64, 'scalar': float64_scalar_device, + 'spectrum': float64_spectrum_device, "image": float64_image_device + }, + { + 'type': numpy.double, 'scalar': double_scalar_device, + 'spectrum': double_spectrum_device, "image": double_image_device + }, + { + 'type': numpy.uint8, 'scalar': uint8_scalar_device, + 'spectrum': uint8_spectrum_device, "image": uint8_image_device + }, + { + 'type': numpy.uint16, 'scalar': uint16_scalar_device, + 'spectrum': uint16_spectrum_device, "image": uint16_image_device + }, + { + 'type': numpy.uint32, 'scalar': uint32_scalar_device, + 'spectrum': uint32_spectrum_device, "image": uint32_image_device + }, + { + 'type': numpy.uint64, 'scalar': uint64_scalar_device, + 'spectrum': uint64_spectrum_device, "image": uint64_image_device + }, + { + 'type': numpy.int16, 'scalar': int16_scalar_device, + 'spectrum': int16_spectrum_device, "image": int16_image_device + }, + { + 'type': numpy.int32, 'scalar': int32_scalar_device, + 'spectrum': int32_spectrum_device, "image": int32_image_device + }, + { + 'type': numpy.int64, 'scalar': int64_scalar_device, + 'spectrum': int64_spectrum_device, "image": int64_image_device + } + ] + + def test_scalar_R(self): + for attribute_type_test in self.attribute_type_tests: + self.read_R_test( + attribute_type_test['scalar'], attribute_type_test['type'], + 'scalar') + + def test_scalar_RW(self): + for attribute_type_test in self.attribute_type_tests: + self.read_RW_test( + attribute_type_test['scalar'], attribute_type_test['type'], + 'scalar') + + def test_scalar_W(self): + for attribute_type_test in self.attribute_type_tests: + self.write_RW_test( + attribute_type_test['scalar'], attribute_type_test['type'], + 'scalar') + + def test_scalar_readback(self): + for attribute_type_test in self.attribute_type_tests: + self.readback_test( + attribute_type_test['scalar'], attribute_type_test['type'], + 'scalar') + + def test_spectrum_R(self): + for attribute_type_test in self.attribute_type_tests: + self.read_R_test( + attribute_type_test['spectrum'], attribute_type_test['type'], + 'spectrum') + + def test_spectrum_RW(self): + for attribute_type_test in self.attribute_type_tests: + self.read_RW_test( + attribute_type_test['spectrum'], attribute_type_test['type'], + 'spectrum') + + def test_spectrum_W(self): + for attribute_type_test in self.attribute_type_tests: + self.write_RW_test( + attribute_type_test['spectrum'], attribute_type_test['type'], + 'spectrum') + + def test_spectrum_readback(self): + for attribute_type_test in self.attribute_type_tests: + self.readback_test( + attribute_type_test['spectrum'], attribute_type_test['type'], + 'spectrum') + + def test_image_R(self): + for attribute_type_test in self.attribute_type_tests: + self.read_R_test( + attribute_type_test['image'], attribute_type_test['type'], + 'image') + + def test_image_RW(self): + for attribute_type_test in self.attribute_type_tests: + self.read_RW_test( + attribute_type_test['image'], attribute_type_test['type'], + 'image') + + def test_image_W(self): + for attribute_type_test in self.attribute_type_tests: + self.write_RW_test(attribute_type_test['image'], attribute_type_test['type'], 'image') + + def test_image_readback(self): + for attribute_type_test in self.attribute_type_tests: + self.readback_test( + attribute_type_test['image'], attribute_type_test['type'], + 'image') diff --git a/devices/test/clients/test_client.py b/tangostationcontrol/tangostationcontrol/test/clients/test_client.py similarity index 70% rename from devices/test/clients/test_client.py rename to tangostationcontrol/tangostationcontrol/test/clients/test_client.py index 355b4f72ad8d6c61c6655b45b75a9f597ac6b72c..ea03e850d4021d0d4c40e82a60d4fd1f0a9d67ea 100644 --- a/devices/test/clients/test_client.py +++ b/tangostationcontrol/tangostationcontrol/test/clients/test_client.py @@ -1,9 +1,12 @@ -from clients.comms_client import CommClient +# External imports import numpy -import os -# <class 'numpy.bool_'> +# Test imports +from tangostationcontrol.clients.comms_client import CommClient + +import logging +logger = logging.getLogger() class test_client(CommClient): """ @@ -14,30 +17,26 @@ class test_client(CommClient): def start(self): super().start() - def __init__(self, fault_func, streams, try_interval=2): + def __init__(self, fault_func, try_interval=2): """ initialises the class and tries to connect to the client. """ - super().__init__(fault_func, streams, try_interval) + super().__init__(fault_func, try_interval) # Explicitly connect - if not self.connect(): - # hardware or infra is down -- needs fixing first - fault_func() - return + self.connect() def connect(self): """ this function provides a location for the code neccecary to connect to the client """ - self.streams.debug_stream("the example client doesn't actually connect to anything silly") + logger.debug("the example client doesn't actually connect to anything silly") self.connected = True # set connected to true - return True # if succesfull, return true. otherwise return false def disconnect(self): self.connected = False # always force a reconnect, regardless of a successful disconnect - self.streams.debug_stream("disconnected from the 'client' ") + logger.debug("disconnected from the 'client' ") def _setup_annotation(self, annotation): """ @@ -53,7 +52,7 @@ class test_client(CommClient): """ # as this is an example, just print the annotation - self.streams.debug_stream("annotation: {}".format(annotation)) + logger.debug("annotation: {}".format(annotation)) def _setup_value_conversion(self, attribute): """ @@ -75,20 +74,22 @@ class test_client(CommClient): takes all gathered data to configure and return the correct read and write functions """ - value = numpy.zeros(dims, dtype) + self.value = numpy.zeros(dims, dtype) def read_function(): - self.streams.debug_stream("from read_function, reading {} array of type {}".format(dims, dtype)) - return value + logger.debug("from read_function, reading {} array of type {}".format(dims, dtype)) + return self.value def write_function(write_value): - self.streams.debug_stream("from write_function, writing {} array of type {}".format(dims, dtype)) - value = write_value + logger.debug("from write_function, writing {} array of type {}".format(dims, dtype)) + + self.value = write_value + return - self.streams.debug_stream("created and bound example_client read/write functions to attribute_wrapper object") + logger.debug("created and bound example_client read/write functions to attribute_wrapper object") return read_function, write_function - def setup_attribute(self, annotation=None, attribute=None): + async def setup_attribute(self, annotation=None, attribute=None): """ MANDATORY function: is used by the attribute wrapper to get read/write functions. must return the read and write functions diff --git a/tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client.py b/tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client.py new file mode 100644 index 0000000000000000000000000000000000000000..c1c29ee04279bab3c943ccc35d4e3a5071345607 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client.py @@ -0,0 +1,260 @@ +import numpy +import asyncua +import io + +import asynctest +from unittest import mock + + +from tangostationcontrol.clients.opcua_client import OPCUAConnection +from tangostationcontrol.clients import opcua_client + +from tangostationcontrol.test import base + + +class attr_props: + def __init__(self, numpy_type): + self.numpy_type = numpy_type + + +attr_test_types = [ + attr_props(numpy_type=str), + attr_props(numpy_type=numpy.bool_), + attr_props(numpy_type=numpy.float32), + attr_props(numpy_type=numpy.float64), + attr_props(numpy_type=numpy.double), + attr_props(numpy_type=numpy.uint8), + attr_props(numpy_type=numpy.uint16), + attr_props(numpy_type=numpy.uint32), + attr_props(numpy_type=numpy.uint64), + attr_props(numpy_type=numpy.int16), + attr_props(numpy_type=numpy.int32), + attr_props(numpy_type=numpy.int64) +] + +scalar_shape = (1,) +spectrum_shape = (4,) +image_shape = (2, 3) +dimension_tests = [scalar_shape, spectrum_shape, image_shape] + + +class TestOPCua(base.AsyncTestCase): + @asynctest.patch.object(OPCUAConnection, "ping") + @asynctest.patch.object(opcua_client, "Client") + async def test_opcua_connection(self, m_opc_client, m_ping): + """ + This tests verifies whether the correct connection steps happen. It checks whether we can init an OPCUAConnection object + Whether we can set the namespace, and the OPCua client. + """ + + m_opc_client_members = asynctest.asynctest.CoroutineMock() + m_opc_client_members.get_namespace_index = asynctest.asynctest.CoroutineMock(return_value=42) + m_opc_client_members.connect = asynctest.asynctest.CoroutineMock() + m_opc_client_members.disconnect = asynctest.asynctest.CoroutineMock() + m_opc_client_members.send_hello = asynctest.asynctest.CoroutineMock() + m_opc_client.return_value = m_opc_client_members + + test_client = OPCUAConnection("opc.tcp://localhost:4874/freeopcua/server/", "http://lofar.eu", 5, mock.Mock(), self.loop) + try: + await test_client.start() + + m_opc_client.assert_called_once() # makes sure the actual freeOPCua client object is created only once + + # this also implies test_client.connect() is called + m_opc_client_members.get_namespace_index.assert_called_once_with("http://lofar.eu") + self.assertEqual(42, test_client.name_space_index) + finally: + await test_client.stop() + + + @asynctest.patch.object(OPCUAConnection, "ping") + @asynctest.patch.object(opcua_client, "Client") + @asynctest.patch.object(opcua_client, 'ProtocolAttribute') + async def test_opcua_attr_setup(self, m_protocol_attr, m_opc_client, m_ping): + """ + This tests covers the correct creation of read/write functions. + In normal circumstances called by he attribute wrapper. + Will be given 'comms_annotation', for OPCua that will be a node path and can access the attributes type and dimensions + + Test succeeds if there are no errors. + """ + + m_opc_client_members = asynctest.asynctest.CoroutineMock() + m_opc_client_members.get_namespace_index = asynctest.asynctest.CoroutineMock(return_value=2) + m_opc_client_members.connect = asynctest.asynctest.CoroutineMock() + m_opc_client_members.disconnect = asynctest.asynctest.CoroutineMock() + m_opc_client_members.send_hello = asynctest.asynctest.CoroutineMock() + m_objects_node = asynctest.Mock() + m_objects_node.get_child = asynctest.asynctest.CoroutineMock() + m_opc_client_members.get_objects_node = asynctest.Mock(return_value=m_objects_node) + m_opc_client.return_value = m_opc_client_members + + for i in attr_test_types: + class mock_attr: + def __init__(self, dtype, x, y): + self.numpy_type = dtype + self.dim_x = x + self.dim_y = y + + for j in dimension_tests: + if len(j) == 1: + dim_x = j[0] + dim_y = 0 + else: + dim_x = j[1] + dim_y = j[0] + + # create a fake attribute with only the required variables in it. + m_attribute = mock_attr(i.numpy_type, dim_x, dim_y) + + # pretend like there is a running OPCua server with a node that has this name + m_annotation = ["2:PCC", f"2:testNode_{str(i.numpy_type)}_{str(dim_x)}_{str(dim_y)}"] + + test_client = OPCUAConnection("opc.tcp://localhost:4874/freeopcua/server/", "http://lofar.eu", 5, mock.Mock(), self.loop) + try: + await test_client.start() + await test_client.setup_attribute(m_annotation, m_attribute) + finally: + await test_client.stop() + + # success if there are no errors. + + def test_protocol_attr(self): + """ + This tests finding an OPCua node and returning a valid object with read/write functions. + (This step is normally initiated by the attribute_wrapper) + """ + + # for all datatypes + for i in attr_test_types: + # for all dimensions + for j in dimension_tests: + + node = mock.Mock() + + # handle scalars slightly differently + if len(j) == 1: + dims = (j[0], 0) + else: + dims = (j[1], j[0]) + + ua_type = opcua_client.numpy_to_OPCua_dict[i.numpy_type] + test = opcua_client.ProtocolAttribute(node, dims[0], dims[1], ua_type) + print(test.dim_y, test.dim_x, test.ua_type) + + """ + Part of the test already includes simply not throwing an exception, but for the sake coverage these asserts have also + been added. + """ + self.assertTrue(test.dim_y == dims[1], f"Dimensionality error, ProtocolAttribute.dim_y got: {test.dim_y} expected: {dims[1]}") + self.assertTrue(test.dim_x == dims[0], f"Dimensionality error, ProtocolAttribute.dim_y got: {test.dim_x} expected: {dims[0]}") + self.assertTrue(test.ua_type == ua_type, f"type error. Got: {test.ua_type} expected: {ua_type}") + self.assertTrue(hasattr(test, "write_function"), f"No write function found") + self.assertTrue(hasattr(test, "read_function"), f"No read function found") + + async def test_read(self): + """ + This tests the read functions. + """ + + for j in dimension_tests: + for i in attr_test_types: + def get_test_value(): + return numpy.zeros(j, i.numpy_type) + + async def get_flat_value(): + return get_test_value().flatten() + + m_node = asynctest.asynctest.CoroutineMock() + + if len(j) == 1: + test = opcua_client.ProtocolAttribute(m_node, j[0], 0, opcua_client.numpy_to_OPCua_dict[i.numpy_type]) + else: + test = opcua_client.ProtocolAttribute(m_node, j[1], j[0], opcua_client.numpy_to_OPCua_dict[i.numpy_type]) + m_node.get_value = get_flat_value + val = await test.read_function() + + comp = val == get_test_value() + self.assertTrue(comp.all(), "Read value unequal to expected value: \n\t{} \n\t{}".format(val, get_test_value())) + + def test_type_map(self): + for numpy_type, opcua_type in opcua_client.numpy_to_OPCua_dict.items(): + # derive a default value that can get lost in a type translation + if numpy_type in [str, numpy.str]: + default_value = "foo" + elif numpy_type == numpy.bool_: + default_value = True + else: + # integer or float type + # integers: numpy will drop the decimals for us + # floats: make sure we chose a value that has an exact binary representation + default_value = 42.25 + + # apply our mapping + v = asyncua.ua.uatypes.Variant(Value=numpy_type(default_value), VariantType=opcua_type) + + try: + # try to convert it to binary to force opcua to parse the value as the type + binary = asyncua.ua.ua_binary.variant_to_binary(v) + + # reinterpret the resulting binary to obtain what opcua made of our value + binary_stream = io.BytesIO(binary) + reparsed_v = asyncua.ua.ua_binary.variant_from_binary(binary_stream) + except Exception as e: + raise Exception(f"Conversion {numpy_type} -> {opcua_type} failed.") from e + + # did the value get lost in translation? + self.assertEqual(v.Value, reparsed_v.Value, msg=f"Conversion {numpy_type} -> {opcua_type} failed.") + + # does the OPC-UA type have the same datasize (and thus, precision?) + if numpy_type not in [str, numpy.str]: + self.assertEqual(numpy_type().itemsize, getattr(asyncua.ua.ua_binary.Primitives, opcua_type.name).size, msg=f"Conversion {numpy_type} -> {opcua_type} failed: precision mismatch") + + + + async def test_write(self): + """ + Test the writing of values by instantiating a ProtocolAttribute attribute, and calling the write function. + but the opcua function that writes to the server has been changed to the compare_values function. + This allows the code to compare what values we want to write and what values would be given to a server. + """ + + # for all dimensionalities + for j in dimension_tests: + + #for all datatypes + for i in attr_test_types: + + # get numpy array of the test value + def get_test_value(): + return numpy.zeros(j, i.numpy_type) + + # get opcua Varianttype array of the test value + def get_mock_value(value): + return asyncua.ua.uatypes.Variant(Value=value, VariantType=opcua_client.numpy_to_OPCua_dict[i.numpy_type]) + + m_node = asynctest.asynctest.CoroutineMock() + + # create the protocolattribute + if len(j) == 1: + test = opcua_client.ProtocolAttribute(m_node, j[0], 0, opcua_client.numpy_to_OPCua_dict[i.numpy_type]) + else: + test = opcua_client.ProtocolAttribute(m_node, j[1], j[0], opcua_client.numpy_to_OPCua_dict[i.numpy_type]) + + # comparison function that replaces `set_data_value` inside the attributes write function + async def compare_values(val): + # test valuest + val = val.tolist() if type(val) == numpy.ndarray else val + if j != dimension_tests[0]: + comp = val.Value == get_mock_value(get_test_value().flatten()).Value + self.assertTrue(comp.all(), + "Array attempting to write unequal to expected array: \n\t got: {} \n\texpected: {}".format(val,get_mock_value(get_test_value()))) + else: + comp = val == get_mock_value(get_test_value()) + self.assertTrue(comp, "value attempting to write unequal to expected value: \n\tgot: {} \n\texpected: {}".format(val, get_mock_value(get_test_value()))) + + # replace the `set_data_value`, usualy responsible for communicating with the server with the `compare_values` function. + m_node.set_data_value = compare_values + + # call the write function with the test values + await test.write_function(get_test_value()) diff --git a/tangostationcontrol/tangostationcontrol/test/clients/test_statistics_client_thread.py b/tangostationcontrol/tangostationcontrol/test/clients/test_statistics_client_thread.py new file mode 100644 index 0000000000000000000000000000000000000000..17f866871bd682b3f289364c16a55e5ee2010a7c --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/test/clients/test_statistics_client_thread.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +import logging +from unittest import mock + +from tangostationcontrol.clients.statistics_client_thread import \ + StatisticsClientThread + +from tangostationcontrol.test import base + +logger = logging.getLogger() + + +class TestStatisticsClientThread(base.TestCase): + + def setUp(self): + super(TestStatisticsClientThread, self).setUp() + + class DummySCThread(StatisticsClientThread): + + def disconnect(self): + pass + + @property + def _options(self) -> dict: + return {} + + @mock.patch.object(DummySCThread, "disconnect") + def test_del_disconnect(self, m_disconnect): + """Ensure that __del__ calls disconnect() of child class""" + + t_test = TestStatisticsClientThread.DummySCThread() + del t_test + + m_disconnect.assert_called_once_with() diff --git a/tangostationcontrol/tangostationcontrol/test/clients/test_tcp_replicator.py b/tangostationcontrol/tangostationcontrol/test/clients/test_tcp_replicator.py new file mode 100644 index 0000000000000000000000000000000000000000..04c87f1d5a15705f7d0b8ce1460141255cb02b27 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/test/clients/test_tcp_replicator.py @@ -0,0 +1,218 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +import logging +import time +from unittest import mock + +import timeout_decorator + +from tangostationcontrol.clients.tcp_replicator import TCPReplicator +from tangostationcontrol.clients import tcp_replicator + +from tangostationcontrol.test import base + +logger = logging.getLogger() + + +class TestTCPReplicator(base.TestCase): + + @staticmethod + async def dummy_task(): + pass + + def setUp(self): + super(TestTCPReplicator, self).setUp() + + self.m_server = mock.Mock() + self.m_server.wait_closed.return_value = self.dummy_task() + + async def dummy_create_server(): + return self.m_server + + # Create reusable test fixture for unit tests + self.m_tcp_replicator = TCPReplicator + + # Patch _run_server and force match spec + event_loop_patcher = mock.patch.object( + tcp_replicator.asyncio, 'get_event_loop') + self.m_event_loop = event_loop_patcher.start() + self.m_event_loop.return_value.create_server.return_value = \ + dummy_create_server() + self.addCleanup(event_loop_patcher.stop) + + # Stash _process_queue before mocking + self.t_process_queue = TCPReplicator._process_queue + + # Patch _process_queue and force match spec + process_queue_patcher = mock.patch.object( + self.m_tcp_replicator, '_process_queue', + autospec=True, return_value=self.dummy_task()) + self.m_process_queue = process_queue_patcher.start() + self.addCleanup(process_queue_patcher.stop) + + def test_parse_options(self): + """Validate option parsing""" + + # Perform string copy of current tcp_bind value + t_tcp_bind = str(TCPReplicator._default_options['tcp_bind']) + + test_options = { + "random": 12346, # I should be ignored + "tcp_bind": '0.0.0.0', # I should get set + } + + replicator = self.m_tcp_replicator(options=test_options) + self.assertTrue(replicator.is_alive()) + + # Ensure replicator initialization does not modify static variable + self.assertEqual(t_tcp_bind, TCPReplicator._default_options['tcp_bind']) + + # Ensure options are correctly updated upon initialization + self.assertEqual(test_options['tcp_bind'], replicator.options['tcp_bind']) + + # Ensure non existing keys don't propagate into options + self.assertFalse('random' in replicator.options) + + def test_connected_clients(self): + """Validate shared list behavior between TCPServerProtocol and thread""" + + m_client = mock.Mock() + + # Create both a TCPReplicator and TCPServerProtocol separately + replicator = self.m_tcp_replicator() + self.assertTrue(replicator.is_alive()) + protocol = TCPReplicator.TCPServerProtocol( + replicator._options, replicator._connected_clients) + + # Add a mocked client to replicators list + replicator._connected_clients.append(m_client) + + # Ensure the mocked client appears in the protocols list + self.assertTrue(m_client in protocol.connected_clients) + + def test_start_stop(self): + """Verify threading behavior, being able to start and stop the thread""" + + replicator = self.m_tcp_replicator() + self.assertTrue(replicator.is_alive()) + + # Give the thread 5 seconds to stop + replicator.join(5) + + # Thread should now be dead + self.assertFalse(replicator.is_alive()) + + @timeout_decorator.timeout(5) + def test_start_except_eventloop(self): + """Verify exception handling inside run() for eventloop creation""" + + m_loop = mock.Mock() + m_loop.create_task.side_effect = RuntimeError("Test Error") + + # Signal to _clean_shutdown that the exception has caused the loop to + # stop + m_loop.is_running.return_value = False + + m_replicator_import = tcp_replicator + + with mock.patch.object(m_replicator_import, 'asyncio') as run_patcher: + run_patcher.new_event_loop.return_value = m_loop + + # Constructor should raise an exception if the thread dies early + self.assertRaises(RuntimeError, self.m_tcp_replicator) + + @timeout_decorator.timeout(5) + def test_start_except_server(self): + """Verify exception handling inside run() for starting server""" + + self.m_event_loop.return_value.create_server.side_effect =\ + RuntimeError("Test Error") + + # Constructor should raise an exception if the thread dies early + self.assertRaises(RuntimeError, self.m_tcp_replicator) + + @timeout_decorator.timeout(5) + def test_start_stop_delete(self): + """Verify that deleting the TCPReplicator object safely halts thread""" + + replicator = self.m_tcp_replicator() + self.assertTrue(replicator.is_alive()) + + del replicator + + def test_transmit(self): + """Test that clients are getting data written to their transport""" + + m_data = "Hello World!".encode('utf-8') + + m_client = mock.Mock() + + replicator = self.m_tcp_replicator() + self.assertTrue(replicator.is_alive()) + + replicator._connected_clients.append(m_client) + + replicator.transmit(m_data) + + # TODO(Corne): Find suitable primitive to synchronize async task update + # with main thread. + time.sleep(1) + time.sleep(1) + time.sleep(1) + time.sleep(1) + time.sleep(1) + time.sleep(1) + + m_client.transport.write.assert_called_once_with(m_data) + + def test_queue_start(self): + replicator = self.m_tcp_replicator() + + self.m_process_queue.assert_called_once_with(replicator) + + def test_transmit_queue(self): + m_data = "Hello World!".encode('utf-8') + + m_client = mock.Mock() + + replicator = self.m_tcp_replicator() + self.assertTrue(replicator.is_alive()) + + # Patch _process_queue back into object and jump start it + replicator._process_queue = self.t_process_queue + replicator._loop.call_soon_threadsafe( + replicator._loop.create_task, replicator._process_queue(replicator)) + + replicator._connected_clients.append(m_client) + + replicator.put(m_data) + + # TODO(Corne): Find suitable primitive to synchronize async task update + # with main thread. + time.sleep(1) + time.sleep(1) + time.sleep(1) + time.sleep(1) + time.sleep(1) + time.sleep(1) + + m_client.transport.write.assert_called_once_with(m_data) + + def test_disconnect(self,): + m_client = mock.Mock() + + replicator = self.m_tcp_replicator() + self.assertTrue(replicator.is_alive()) + + replicator._connected_clients.append(m_client) + + replicator.join(5) + + m_client.transport.abort.assert_called_once_with() diff --git a/tangostationcontrol/tangostationcontrol/test/common/__init__.py b/tangostationcontrol/tangostationcontrol/test/common/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tangostationcontrol/tangostationcontrol/test/common/test_baselines.py b/tangostationcontrol/tangostationcontrol/test/common/test_baselines.py new file mode 100644 index 0000000000000000000000000000000000000000..25eb5d1dfffa2fca8748d74020893edfb17c2037 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/test/common/test_baselines.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +from tangostationcontrol.common import baselines + +from tangostationcontrol.test import base + + +class TestBaselines(base.TestCase): + def test_nr_baselines(self): + # no baselines if no antennas + self.assertEqual(0, baselines.nr_baselines(0)) + # one antenna only has autocorrelation + self.assertEqual(1, baselines.nr_baselines(1)) + # two antennas each have autocorrelations + a baseline between each other + self.assertEqual(3, baselines.nr_baselines(2)) + + def test_baseline_indices(self): + """ Test whether baseline_from_index and baseline_index line up. """ + + for major in range(192): + for minor in range(major + 1): + idx = baselines.baseline_index(major, minor) + self.assertEqual((major, minor), baselines.baseline_from_index(idx), msg=f'baseline_index({major},{minor}) resulted in {idx}, and should match baseline_from_index({idx})') diff --git a/devices/test/common/test_lofar_logging.py b/tangostationcontrol/tangostationcontrol/test/common/test_lofar_logging.py similarity index 76% rename from devices/test/common/test_lofar_logging.py rename to tangostationcontrol/tangostationcontrol/test/common/test_lofar_logging.py index 534b2650c8d432ef7c7dae9e32448b01cc5913f3..5140d05e58ca370509a080211ca96f2df21fe399 100644 --- a/devices/test/common/test_lofar_logging.py +++ b/tangostationcontrol/tangostationcontrol/test/common/test_lofar_logging.py @@ -7,15 +7,16 @@ # Distributed under the terms of the APACHE license. # See LICENSE.txt for more info. -import git from unittest import mock - -from common import lofar_logging import logging + from tango.server import Device from tango import device_server +from tango.test_context import DeviceTestContext + +from tangostationcontrol.common import lofar_logging -from test import base +from tangostationcontrol.test import base class TestLofarLogging(base.TestCase): @@ -75,18 +76,22 @@ class TestLofarLogging(base.TestCase): # create a Tango Device that logs something class MyDevice(Device): - def __init__(self): + def init_device(self): self.log_deeper_in_stack() def log_deeper_in_stack(self): logger.info("test") with mock.patch.object(device_server.DeviceImpl, '__info_stream') as m_info_stream: - # logs in the constructor already - mydevice = MyDevice() + with DeviceTestContext(MyDevice) as mydevice: + self.assertEqual(1, m_info_stream.call_count, msg="configure_logger did not send logs to active Tango device") + + # Lookup our "test" logline among f.e. the debug messages output by Tango + test_record = [record for record in self.memory_handler.records if record.msg == "test"] - self.assertEqual(mydevice, self.memory_handler.records[0].tango_device, msg="configure_logging did not detect active Tango device") - self.assertEqual(1, m_info_stream.call_count, msg="configure_logger did not send logs to active Tango device") + # Tango uses slightly different class representations of MyDevice, so + # we can't compare them direclty. Just verify we're talking about the same thing. + self.assertEqual(str(mydevice), str(test_record[0].tango_device), msg="configure_logging did not detect active Tango device") def test_log_exceptions(self): diff --git a/tangostationcontrol/tangostationcontrol/test/common/test_lofar_version.py b/tangostationcontrol/tangostationcontrol/test/common/test_lofar_version.py new file mode 100644 index 0000000000000000000000000000000000000000..89ac894d9388d3939671c43239033ce1147fef30 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/test/common/test_lofar_version.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +import git +from unittest import mock + +from tangostationcontrol.common import lofar_version + +from tangostationcontrol.test import base + + +class TestLofarVersion(base.TestCase): + + def setUp(self): + super(TestLofarVersion, self).setUp() + + # Clear the cache as this function of lofar_version uses LRU decorator + # This is a good demonstration of how unit tests in Python can have + # permanent effects, typically fixtures are needed to restore these. + lofar_version.get_version.cache_clear() + + def test_get_version(self): + """Test if attributes of get_repo are correctly used by get_version""" + + with mock.patch.object(lofar_version, 'get_repo') as m_get_repo: + m_commit = mock.Mock() + m_commit.return_value.__str__ = mock.Mock(return_value="123456") + m_commit.return_value.iter_items.return_value = [] + + m_is_dirty = mock.Mock() + m_is_dirty.return_value = False + + m_head = mock.Mock(is_detached=False) + + m_get_repo.return_value = mock.Mock( + active_branch="main", commit=m_commit, tags=[], + is_dirty=m_is_dirty, head=m_head) + + # No need for special string equal in Python + self.assertEqual("0.0.0.main.123456", lofar_version.get_version()) + + def test_get_version_tag(self): + """Test if get_version determines production_ready for tagged commit""" + + with mock.patch.object(lofar_version, 'get_repo') as m_get_repo: + m_commit = mock.Mock() + m_commit.return_value.__str__ = mock.Mock(return_value="123456") + m_commit.return_value.iter_items.return_value = [] + + m_is_dirty = mock.Mock() + m_is_dirty.return_value = False + + m_head = mock.Mock(is_detached=False) + + m_tag_commit = mock.Mock(type="commit") + m_tag_commit.__str__ = mock.Mock(return_value="123456") + + m_tag = mock.Mock(commit=m_tag_commit) + m_tag.name = "v0.0.3" + m_tag.__str__ = mock.Mock(return_value= "v0.0.3") + + m_commit.return_value = m_tag_commit + m_commit.return_value.iter_items.return_value = [m_tag_commit] + + m_get_repo.return_value = mock.Mock( + active_branch="main", commit=m_commit, + tags=[m_tag], is_dirty=m_is_dirty, head=m_head) + + self.assertEqual("0.0.3", lofar_version.get_version()) + + @mock.patch.object(lofar_version, 'get_repo') + def test_get_version_tag_dirty(self, m_get_repo): + + """Test if get_version determines dirty tagged commit""" + m_commit = mock.Mock() + m_commit.return_value.__str__ = mock.Mock(return_value="123456") + m_commit.return_value.iter_items.return_value = [] + + m_is_dirty = mock.Mock() + m_is_dirty.return_value = True + + m_head = mock.Mock(is_detached=False) + + m_get_repo.return_value = mock.Mock( + active_branch="main", commit=m_commit, tags=[], + is_dirty=m_is_dirty, head=m_head) + + # No need for special string equal in Python + self.assertEqual("0.0.0.main.123456.dirty", lofar_version.get_version()) + + def test_catch_repo_error(self): + """Test if invalid git directories will raise error""" + + with mock.patch.object(lofar_version, 'get_repo') as m_get_repo: + + # Configure lofar_version.get_repo to raise InvalidGitRepositoryError + m_get_repo.side_effect = git.InvalidGitRepositoryError + + # Test that error is raised by get_version + self.assertRaises( + git.InvalidGitRepositoryError, lofar_version.get_version) diff --git a/tangostationcontrol/tangostationcontrol/test/devices/__init__.py b/tangostationcontrol/tangostationcontrol/test/devices/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/devices/test/devices/automatic_polling_performance_test/Tango_Controls-Automatic_polling_performance_test.md b/tangostationcontrol/tangostationcontrol/test/devices/automatic_polling_performance_test/Tango_Controls-Automatic_polling_performance_test.md similarity index 100% rename from devices/test/devices/automatic_polling_performance_test/Tango_Controls-Automatic_polling_performance_test.md rename to tangostationcontrol/tangostationcontrol/test/devices/automatic_polling_performance_test/Tango_Controls-Automatic_polling_performance_test.md diff --git a/devices/test/devices/automatic_polling_performance_test/automatic_polling_performance_test.json b/tangostationcontrol/tangostationcontrol/test/devices/automatic_polling_performance_test/automatic_polling_performance_test.json similarity index 100% rename from devices/test/devices/automatic_polling_performance_test/automatic_polling_performance_test.json rename to tangostationcontrol/tangostationcontrol/test/devices/automatic_polling_performance_test/automatic_polling_performance_test.json diff --git a/devices/test/devices/automatic_polling_performance_test/monitoring_performance_test.py b/tangostationcontrol/tangostationcontrol/test/devices/automatic_polling_performance_test/monitoring_performance_test.py similarity index 100% rename from devices/test/devices/automatic_polling_performance_test/monitoring_performance_test.py rename to tangostationcontrol/tangostationcontrol/test/devices/automatic_polling_performance_test/monitoring_performance_test.py diff --git a/devices/test/devices/random_data.py b/tangostationcontrol/tangostationcontrol/test/devices/random_data.py similarity index 97% rename from devices/test/devices/random_data.py rename to tangostationcontrol/tangostationcontrol/test/devices/random_data.py index 43e1a037624a516f88d05d644fd86e23fab6baa8..51d7b269f99c814cb340dc9f0e8feb44f3393f94 100644 --- a/devices/test/devices/random_data.py +++ b/tangostationcontrol/tangostationcontrol/test/devices/random_data.py @@ -7,13 +7,6 @@ # Distributed under the terms of the APACHE license. # See LICENSE.txt for more info. -# TODO(Corne): Remove sys.path.append hack once packaging is in place! -import os, sys -currentdir = os.path.dirname(os.path.realpath(__file__)) -parentdir = os.path.dirname(currentdir) -parentdir = os.path.dirname(parentdir) -sys.path.append(parentdir) - # PyTango imports from tango import DevState from tango.server import run, Device, attribute, command @@ -497,6 +490,3 @@ def main(args = None, **kwargs): Main function of the RandomData module. """ return run((Random_Data,), args = args, **kwargs) - -if __name__ == '__main__': - main() diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_abstract_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_abstract_device.py new file mode 100644 index 0000000000000000000000000000000000000000..469ea19fa970cf48c2de9c4c6b5648bd7b756040 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/test/devices/test_abstract_device.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +import abc +from unittest import mock + +from tango import DevFailed +from tango import server +from tango.server import attribute + +from tango.test_context import DeviceTestContext + +from tangostationcontrol.devices.abstract_device import AbstractDeviceMetas + +from tangostationcontrol.test import base + + +class TestAbstractDevice(base.TestCase): + + class AbstractExample(object, metaclass=abc.ABCMeta): + """A pure abc.ABCMeta metaclass with an abstract method + + This is an abstract class that inherits object with the abc.ABCMeta as + metaclass + """ + + @abc.abstractmethod + def example_method(self): + raise NotImplementedError + + class TestHardwareDevice(server.Device, metaclass=AbstractDeviceMetas): + """This is your overarching abstract class with a combined metaclass + + Device is an object with DeviceMeta as metaclass + We use HardwareDeviceMetas as metaclass + + Our metaclass contract is now fulfilled. + """ + + @attribute(dtype=float) + def call_example_method(self): + return self.example_method() + + @abc.abstractmethod + def example_method(self): + raise NotImplementedError + + class ConcreteHardwareDevice(TestHardwareDevice): + + def example_method(self): + return 12 + + def setUp(self): + super(TestAbstractDevice, self).setUp() + + def test_instance_tango(self): + + try: + with DeviceTestContext(self.TestHardwareDevice, process=True) as proxy: + # Calling this method raises the NotImplementedError exception + proxy.call_example_method() + except Exception as e: + self.assertIsInstance(e, DevFailed) + + with DeviceTestContext(self.ConcreteHardwareDevice, process=True) as proxy: + self.assertEqual(12, proxy.call_example_method) + + @mock.patch.object(server, 'get_worker') + @mock.patch.object(server, 'LatestDeviceImpl') + def test_instance_error(self, m_worker, m_implement): + # Creating this object should raise a type error but it does not + # combining metaclasses in this way does not have the desired result. + # This is a known limitation of this approach + m_device = self.TestHardwareDevice(mock.Mock(), mock.Mock()) + + # Raising the NotImplementedError works as expected, however. + self.assertRaises(NotImplementedError, m_device.example_method) + + # Creating this object of a class that has a pure metaclass does raise + # the expected error. + self.assertRaises(TypeError, self.AbstractExample) + + diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_statistics_collector.py b/tangostationcontrol/tangostationcontrol/test/devices/test_statistics_collector.py new file mode 100644 index 0000000000000000000000000000000000000000..4b58141c06d9b09d68dba295b007b41081ca3618 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/test/devices/test_statistics_collector.py @@ -0,0 +1,125 @@ +from tangostationcontrol.devices.sdp.statistics_collector import XSTCollector +from tangostationcontrol.devices.sdp.statistics_packet import XSTPacket + +from tangostationcontrol.test import base + +class TestXSTCollector(base.TestCase): + def test_valid_packet(self): + collector = XSTCollector() + + # a valid packet as obtained from SDP, with 64-bit BE 1+1j as payload at (12,0) + packet = b'X\x05\x00\x00\x00\x00\x00\x00\x10\x08\x00\x02\xfa\xef\x00f\x0c\x00\x0c\x08\x01 \x14\x00\x00\x01!\xd9&z\x1b\xb3' + 288 * b'\x00\x00\x00\x00\x00\x00\x00\x01' + + # parse it ourselves to extract info nicely + fields = XSTPacket(packet) + fpga_index = fields.gn_index + + # baseline indeed should be (12,0) + self.assertEqual((12,0), fields.first_baseline) + + # this should not throw + collector.process_packet(packet) + + # counters should now be updated + self.assertEqual(1, collector.parameters["nof_packets"]) + self.assertEqual(0, collector.parameters["nof_invalid_packets"]) + + self.assertEqual(1, collector.parameters["nof_valid_payloads"][fpga_index]) + self.assertEqual(0, collector.parameters["nof_payload_errors"][fpga_index]) + + # check whether the data ended up in the right block, and the rest is still zero + xst_values = collector.xst_values() + + for baseline_a in range(collector.MAX_INPUTS): + for baseline_b in range(collector.MAX_INPUTS): + if baseline_b > baseline_a: + # only scan top-left triangle + continue + + baseline_a_was_in_packet = (fields.first_baseline[0] <= baseline_a < fields.first_baseline[0] + fields.nof_signal_inputs) + baseline_b_was_in_packet = (fields.first_baseline[1] <= baseline_b < fields.first_baseline[1] + fields.nof_signal_inputs) + + if baseline_a_was_in_packet and baseline_b_was_in_packet: + self.assertEqual(1+1j, xst_values[baseline_a][baseline_b], msg=f'element [{baseline_a}][{baseline_b}] did not end up in XST matrix.') + else: + self.assertEqual(0+0j, xst_values[baseline_a][baseline_b], msg=f'element [{baseline_a}][{baseline_b}] was not in packet, but was written to the XST matrix.') + + def test_conjugated_packet(self): + """ Test whether a packet with a baseline (a,b) with a<b will get its payload conjugated. """ + + collector = XSTCollector() + + # a valid packet as obtained from SDP, with 64-bit BE 1+1j as payload, at baseline (0,12) + # VV VV + packet = b'X\x05\x00\x00\x00\x00\x00\x00\x10\x08\x00\x02\xfa\xef\x00f\x00\x0c\x0c\x08\x01 \x14\x00\x00\x01!\xd9&z\x1b\xb3' + 288 * b'\x00\x00\x00\x00\x00\x00\x00\x01' + + # parse it ourselves to extract info nicely + fields = XSTPacket(packet) + + # baseline indeed should be (0,12) + self.assertEqual((0,12), fields.first_baseline) + + # this should not throw + collector.process_packet(packet) + + # counters should now be updated + self.assertEqual(1, collector.parameters["nof_packets"]) + self.assertEqual(0, collector.parameters["nof_invalid_packets"]) + + # check whether the data ended up in the right block, and the rest is still zero + xst_values = collector.xst_values() + + for baseline_a in range(collector.MAX_INPUTS): + for baseline_b in range(collector.MAX_INPUTS): + if baseline_b > baseline_a: + # only scan top-left triangle + continue + + # use swapped indices! + baseline_a_was_in_packet = (fields.first_baseline[1] <= baseline_a < fields.first_baseline[1] + fields.nof_signal_inputs) + baseline_b_was_in_packet = (fields.first_baseline[0] <= baseline_b < fields.first_baseline[0] + fields.nof_signal_inputs) + + if baseline_a_was_in_packet and baseline_b_was_in_packet: + self.assertEqual(1-1j, xst_values[baseline_a][baseline_b], msg=f'element [{baseline_a}][{baseline_b}] did not end up conjugated in XST matrix.') + else: + self.assertEqual(0+0j, xst_values[baseline_a][baseline_b], msg=f'element [{baseline_a}][{baseline_b}] was not in packet, but was written to the XST matrix.') + + def test_invalid_packet(self): + collector = XSTCollector() + + # an invalid packet + # V + packet = b'S\x05\x00\x00\x00\x00\x00\x00\x10\x08\x00\x02\xfa\xef\x00f\x00\x00\x0c\x08\x01 \x14\x00\x00\x01!\xd9&z\x1b\xb3' + 288 * b'\x00\x00\x00\x00\x00\x00\x00\x01' + + # this should throw + with self.assertRaises(ValueError): + collector.process_packet(packet) + + # counters should now be updated + self.assertEqual(1, collector.parameters["nof_packets"]) + self.assertEqual(1, collector.parameters["nof_invalid_packets"]) + + self.assertListEqual([0] * collector.MAX_FPGAS, list(collector.parameters["nof_valid_payloads"])) + self.assertListEqual([0] * collector.MAX_FPGAS, list(collector.parameters["nof_payload_errors"])) + + def test_payload_error(self): + collector = XSTCollector() + + # an valid packet with a payload error + # V + packet = b'X\x05\x00\x00\x00\x00\x00\x00\x14\x08\x00\x02\xfa\xef\x00f\x00\x00\x0c\x08\x01 \x14\x00\x00\x01!\xd9&z\x1b\xb3' + 288 * b'\x00\x00\x00\x00\x00\x00\x00\x01' + + # parse it ourselves to extract info nicely + fields = XSTPacket(packet) + fpga_index = fields.gn_index + + # this should not throw + collector.process_packet(packet) + + # counters should now be updated + self.assertEqual(1, collector.parameters["nof_packets"]) + self.assertEqual(0, collector.parameters["nof_invalid_packets"]) + + self.assertEqual(0, collector.parameters["nof_valid_payloads"][fpga_index]) + self.assertEqual(1, collector.parameters["nof_payload_errors"][fpga_index]) + diff --git a/tangostationcontrol/tangostationcontrol/toolkit/README.md b/tangostationcontrol/tangostationcontrol/toolkit/README.md new file mode 100644 index 0000000000000000000000000000000000000000..babdfac5661f17e9d5b31c5b5d4fa2de20d3b45e --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/toolkit/README.md @@ -0,0 +1,54 @@ +# Tango Archiving Framework + +The Archiver class in archiver.py defines the methods to manage the device attributes archiving allowed by Tango. + +The main components (and the relative Docker containers) are: + +- Configuration Manager (container: hdbpp-cm): Device server that assists in adding, modifying, moving, deleting an Attribute to/from the archiving system +- Event Subscriber (container: hdbpp-es): The EventSubscriber TANGO device server, is the archiving system engine. On typical usage, it will subscribe to archive events on request by the ConfigurationManager device. The EventSubscriber is designed to start archiving all the already configured Attributes, even if the ConfigurationManager is not running. Moreover, being a TANGO device, the EventSubscriber configuration can be managed with Jive. +- Archiving DBMS (container: archiver-maria-db): Specific Database devoted to storing attribute values. +- (Optional) HDB++ Viewer (container: hdbpp-viewer): Standalone JAVA application designed to monitor signals coming from database + +## Archiver creation +When an Archiver object is created, we can define four of its properties: +- the Selector configuration file-name, a JSON file in which environment properties are defined +- the ConfigurationManager name (Tango namespace) +- at least one EventSubscriber name (Tango namespace) +- the default context archiving for the subscribers. This means that a default archiving strategy will be applied to +all the attributes. Of course this strategy can be tuned individually for each attribute if needed. +Archiving strategies are ['ALWAYS','RUN','SHUTDOWN','SERVICE'] +- ALWAYS:always stored +- RUN:stored during run +- SHUTDOWN:stored during shutdown +- SERVICE:stored during maintenance activities + +## Select environment configuration +The Selector object creates a dictionary from a JSON configuration file (if not defined by user, a default lofar2.json is retrieved) +in order to allow a custom starting configuration of the archiving procedure. +In the JSON file, for each Tango device, three variables are defined: +- Environment, which defines the general behaviour of archiving framework, in particular: + - "Development" -> none of the attributes are archived by default + - "Production" -> all the attributes are archived by default +- Include, which defines a list of the attributes that must be added to archiving (to be used in "Development" mode) +- Exclude, which defines a list of the attributes that must be removed from the archiving (to be used in "Production" mode) +The advantages of using such a configuration selection is that every user can load a custom configuration following its necessities. + +## Add an attribute +When adding an attribute to the archiving framework, we must define the following properties: +- the EventSubscriber name that will take charge of the attribute +- the archiving strategy (4 options defined above) +- the attribute polling period (it should have been already defined in TangoDB) +- the archive event period (MOST IMPORTANT, it defines the frequency rate at which an attribute is archived in the DBMS) + +It is important to understand that, when an attribute is successfully added to the EventSubscriber list, the archiving begins without an explicit 'Start' command, rather it follows the archiving strategy already defined. + +The 'Start' command is used instead during a session when an attribute has been paused/stopped for any reason, or it has raised some kind of issue. + +## Difference between Stop and Remove an attribute +When stopping an attribute archiving, the framework does not remove it from the list. +This means that archiving is stopped for the current session, but if the device is restarted, the attribute archiving will be restarted as well. +In order to definitely stop the archiving, the attribute must be removed from the attribute list. + +## Update an attribute +If we want to update the archiving properties of an attribute (e.g. the archive event period), there is a relative method. +It must be noted that the updating is not istantaneous because, following the framework architecture, an attribute must be first removed from the EventSubscriber list and then re-added with the new properties. diff --git a/tangostationcontrol/tangostationcontrol/toolkit/__init__.py b/tangostationcontrol/tangostationcontrol/toolkit/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tangostationcontrol/tangostationcontrol/toolkit/archiver.py b/tangostationcontrol/tangostationcontrol/toolkit/archiver.py new file mode 100644 index 0000000000000000000000000000000000000000..f462725a6bfbe33fe5118bbdb6c4e2ba9bc576e2 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/toolkit/archiver.py @@ -0,0 +1,497 @@ +#! /usr/bin/env python3 + +#from logging import raiseExceptions +import logging +import traceback + +from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper + +from tango import DeviceProxy, AttributeProxy +from datetime import datetime, timedelta + +import time +import json, os +from sqlalchemy import create_engine, and_ +from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm.exc import NoResultFound +from .archiver_base import * + +logger = logging.getLogger() + +def parse_attribute_name(attribute_name:str): + """ + Parse the attribute names since most of the archiving operations require only + Tango full-qualified names. + """ + chunk_num = len(attribute_name.split('/')) + if (chunk_num!=4): + raise AttributeFormatException + +def reduce_attribute_name(attribute_name:str): + """ + For some operations Tango attribute must be transformed from the form 'tango://db:port/domain/family/name/attribute' + to canonical 'domain/family/name/attribute' + """ + chunk_num = len(attribute_name.split('/')) + if (chunk_num==7 and attribute_name.split('/')[0]=='tango:'): + return '/'.join(attribute_name.split('/')[3:]) + else: + if (chunk_num!=4): + raise AttributeFormatException + else: + return attribute_name + +class Archiver(): + """ + The Archiver class implements the basic operations to perform attributes archiving + """ + + # Global 'DEVELOPMENT' environment variables set by configuration file + dev_polling_time = None + dev_archive_time = None + + def __init__(self, selector_filename:str = None, cm_name: str = 'archiving/hdbpp/confmanager01', es_name: str = 'archiving/hdbpp/eventsubscriber01', context: str = 'RUN'): + self.cm_name = cm_name + self.cm = DeviceProxy(cm_name) + try: + cm_state = self.cm.state() # ping the device server + if 'FAULT' in str(cm_state): + raise Exception("Configuration Manager is in FAULT state") + except Exception as e: + raise Exception("Connection failed with Configuration Manager device") from e + self.es_name = es_name + self.es = DeviceProxy(es_name) + self.cm.write_attribute('Context',context) # Set default Context Archiving for all the subscribers + self.selector = Selector() if selector_filename is None else Selector(selector_filename) # Create selector for customized strategies + try: + self.apply_selector() + except Exception as e: + raise Exception("Error in selecting configuration! Archiving framework will not be updated!") from e + + def apply_selector(self): + """ + Apply the customized strategy defined by the selector + """ + config_dict = self.selector.get_dict() + # Set global development env variables + var_dict = config_dict.get('global_variables') + self.dev_polling_time = int(var_dict.get('development_polling_time')) + self.dev_archive_time = int(var_dict.get('development_archive_time')) + # Set devices archiving + env_dict = config_dict.get('devices') + for device in env_dict: + try: + dev_env = str(env_dict[device].get('environment')) # Get device environment + if dev_env == 'development': # DEV environment -> all attributes are excluded by default + include_att_list = env_dict[device].get('include',[]) + self.remove_attributes_by_device(device, exclude=include_att_list) + # Include attributes by custom configuration + for att in include_att_list: + att_fqname = f"{device}/{att}".lower() + self.add_attribute_to_archiver(att_fqname,self.dev_polling_time,self.dev_archive_time) + elif dev_env == 'production': # PROD environment -> all attributes are included by default + exclude_att_list = env_dict[device].get('exclude', []) + self.add_attributes_by_device(device, exclude=exclude_att_list) + # Remove attributes by custom configuration if already present + # The following cycle is a security check in the special case that an attribute is in the + # included list in DEV mode, and in the excluded list in PROD mode + for att in exclude_att_list: + att_fqname = f"{device}/{att}".lower() + self.remove_attribute_from_archiver(att_fqname) + except Exception as e: + if 'API_DeviceNotExported' in str(e): # ignore if device is offline + logger.warning(f"Device {device} offline") + elif 'API_CantConnectToDevice' in str(e): + logger.warning(f"Device {device} not found") + elif 'DB_DeviceNotDefined' in str(e): + logger.warning(f"Device {device} not defined in TangoDB") + else: + raise Exception from e + return env_dict + + def add_attribute_to_archiver(self, attribute_name: str, polling_period: int, event_period: int, strategy: str = 'RUN'): + """ + Takes as input the attribute name, polling period (ms), event period (ms) and archiving strategy, + and adds the selected attribute to the subscriber's list of archiving attributes. + The ConfigurationManager and EventSubscriber devices must be already up and running. + The archiving-DBMS must be already properly configured. + """ + parse_attribute_name(attribute_name) + try: + self.cm.write_attribute('SetAttributeName', attribute_name) + self.cm.write_attribute('SetArchiver', self.es_name) + self.cm.write_attribute('SetStrategy', strategy) + self.cm.write_attribute('SetPollingPeriod', polling_period) + self.cm.write_attribute('SetPeriodEvent', event_period) + self.cm.AttributeAdd() + logger.info(f"Attribute {attribute_name} added to archiving list!") + except Exception as e: + if 'already archived' not in str(e).lower(): + raise Exception from e + else: + logger.warning(f"Attribute {attribute_name} already in archiving list!") + + def add_attributes_by_device(self,device_name,global_archive_period:int = None, exclude:list = []): + """ + Add sequentially all the attributes of the selected device in the event subscriber list, if not already present + """ + d = DeviceProxy(device_name) + dev_attrs_list = d.get_attribute_list() + exclude_list = [a.lower() for a in exclude] + attrs_list = [a.lower() for a in list(dev_attrs_list) if a.lower() not in exclude_list] # transform list for string comparison + for a in attrs_list: + attr_fullname = f"{device_name}/{a}".lower() + attr_proxy = AttributeProxy(attr_fullname) + if attr_proxy.is_polled() is True: # if not polled attribute is also not archived + try: + if self.es.AttributeList is None or not(self.cm.AttributeSearch(a)): + polling_period = attr_proxy.get_poll_period() or self.dev_polling_time + archive_period = global_archive_period or int(attr_proxy.get_property('archive_period')['archive_period'][0]) or self.dev_archive_time + self.add_attribute_to_archiver(attr_fullname,polling_period=polling_period, + event_period=archive_period) + #time.sleep(0.5) + except IndexError as e: + logger.warning(f"Attribute {attr_fullname} will not be archived because archive event period is not defined!") + except Exception as e: + raise Exception from e + else: + logger.warning(f"Attribute {attr_fullname} will not be archived because polling is set to FALSE!") + + def remove_attribute_from_archiver(self, attribute_name:str): + """ + Stops the data archiving of the attribute passed as input, and remove it from the subscriber's list. + """ + parse_attribute_name(attribute_name) + try: + self.cm.AttributeStop(attribute_name) + self.cm.AttributeRemove(attribute_name) + logger.warning(f"Attribute {attribute_name} removed!") + except Exception as e: + if 'attribute not found' not in str(e).lower(): + raise Exception from e + else: + logger.warning(f"Attribute {attribute_name} not found in archiving list!") + + def remove_attributes_by_device(self,device_name:str,exclude:list=[]): + """ + Stops the data archiving of all the attributes of the selected device, and remove them from the + subscriber's list + """ + d = DeviceProxy(device_name) + dev_attrs_list = d.get_attribute_list() + exclude_list = [a.lower() for a in exclude] + attrs_list = [a.lower() for a in list(dev_attrs_list) if a.lower() not in exclude_list] # transform list for string comparison + for a in attrs_list: + try: + attr_fullname = f"{device_name}/{a}".lower() + self.remove_attribute_from_archiver(attr_fullname) + except Exception as e: + raise Exception from e + + def remove_attributes_in_error(self,exclude:list=[],es_name:str=None): + """ + Remove from the subscriber's list all the attributes currently in error (not being archived) + """ + if es_name is not None: + es = DeviceProxy(es_name) + else: + es = self.es + attributes_nok = es.AttributeNokList or [] + exclude_list = [a.lower() for a in exclude] + attrs_list = [a.lower() for a in list(attributes_nok) if a.lower() not in exclude_list] + for a in attrs_list: + attr_fullname = reduce_attribute_name(a) + self.remove_attribute_from_archiver(attr_fullname) + + def start_archiving_attribute(self, attribute_name:str): + """ + Starts the archiving of the attribute passed as input. + The attribute must be already present in the subscriber's list + """ + parse_attribute_name(attribute_name) + try: + self.cm.AttributeStart(attribute_name) + except Exception as e: + if 'attribute not found' not in str(e).lower(): + raise Exception from e + else: + logger.warning(f"Attribute {attribute_name} not found!") + + def stop_archiving_attribute(self, attribute_name:str): + """ + Stops the archiving of the attribute passed as input. + The attribute must be already present in the subscriber's list + """ + parse_attribute_name(attribute_name) + try: + self.cm.AttributeStop(attribute_name) + except Exception as e: + if 'attribute not found' not in str(e).lower(): + raise Exception from e + else: + logger.warning(f"Attribute {attribute_name} not found!") + + def is_attribute_archived(self,attribute_name:str): + """ + Check if an attribute is in the archiving list + """ + parse_attribute_name(attribute_name) + attributes = self.cm.AttributeSearch(attribute_name.lower()) + if len(attributes)>1: + # Handle case same attribute_name r/rw + if len(attributes)==2 and (attributes[0].endswith(attributes[1]+'w') or attributes[1].endswith(attributes[0]+'w')): + return True + else: + raise Exception(f"Multiple Attributes Matched! {attributes}") + elif len(attributes)==1: + return True + else: + return False + + def update_archiving_attribute(self, attribute_name: str, polling_period: int, event_period: int, strategy: str = 'RUN'): + """ + Update the archiving properties of an attribute already in a subscriber list + """ + self.remove_attribute_from_archiver(attribute_name) + time.sleep(3.) + self.add_attribute_to_archiver(attribute_name,polling_period,event_period,strategy) + time.sleep(3.) + self.start_archiving_attribute(attribute_name) + logger.info(f"Attribute {attribute_name} successfully updated!") + + def get_subscriber_attributes(self,es_name:str = None): + """ + Return the list of attributes managed by the event subscriber + """ + if es_name is not None: + es = DeviceProxy(es_name) + else: + es = self.es + attrs = es.AttributeList or [] + return attrs + + def get_subscriber_errors(self,es_name:str = None): + """ + Return a dictionary of the attributes currently in error, defined as AttributeName -> AttributeError + """ + if es_name is not None: + es = DeviceProxy(es_name) + else: + es = self.es + attrs = es.AttributeList or [] + errs = es.AttributeErrorList or [] + return dict((a,e) for a,e in zip(attrs,errs) if e) or {} + + def get_attribute_errors(self,attribute_name:str): + """ + Return the error related to the attribute + """ + parse_attribute_name(attribute_name) + errs_dict = self.get_subscriber_errors() + for e in errs_dict: + if attribute_name in e: + return errs_dict.get(e) + return None + + def get_subscriber_load(self,use_freq:bool=True,es_name:str = None): + """ + Return the estimated load of an archiver, in frequency of records or number + of attributes + """ + if es_name is not None: + es = DeviceProxy(es_name) + else: + es = self.es + if use_freq: + return str(es.AttributeRecordFreq)+(' events/period' ) + else: + return len(es.AttributeList or []) + + def get_attribute_freq(self,attribute_name:str): + """ + Return the attribute archiving frequency in events/minute + """ + parse_attribute_name(attribute_name) + if self.is_attribute_archived(attribute_name): + freq_dict = dict((a,r) for a,r in zip(self.es.AttributeList,self.es.AttributeRecordFreqList)) + for f in freq_dict: + if attribute_name.lower() in f: + return freq_dict.get(f,0.) + else: + logger.warning(f"Attribute {attribute_name} not found!") + + def get_attribute_failures(self,attribute_name:str): + """ + Return the attribute failure archiving frequency in events/minute + """ + parse_attribute_name(attribute_name) + if self.is_attribute_archived(attribute_name): + fail_dict = dict((a,r) for a,r in zip(self.es.AttributeList,self.es.AttributeFailureFreqList)) + for f in fail_dict: + if attribute_name.lower() in f: + return fail_dict.get(f,0.) + else: + logger.warning(f"Attribute {attribute_name} not found!") + +class AttributeFormatException(Exception): + """ + Exception that handles wrong attribute naming + """ + def __init__(self, message="Wrong Tango attribute format! Try: domain/family/member/attribute (e.g. STAT/RECV/1/temperature)"): + self.message = message + super().__init__(self.message) + +class Selector(): + """ + The Selector class implements operations on select customized retrieval strategies + """ + + def __init__(self, filename='lofar2.json'): + self.filename = filename + + def get_dict(self): + """ + Create a dictionary from the JSON file + """ + try: + filepath = os.path.join(os.path.dirname(__file__),'archiver_config',self.filename) + f = open(filepath) + data = json.load(f) + f.close() + except FileNotFoundError as e: + raise Exception("JSON configuration file not found!") from e + return data + +class Retriever(): + """ + The Retriever class implements retrieve operations on a given DBMS + """ + def __init__(self, cm_name: str = 'archiving/hdbpp/confmanager01'): + self.cm_name = cm_name + self.session = self.connect_to_archiving_db() + + def get_db_credentials(self): + """ + Retrieves the DB credentials from the Tango properties of Configuration Manager + """ + cm = DeviceProxy(self.cm_name) + config_list = cm.get_property('LibConfiguration')['LibConfiguration'] # dictionary {'LibConfiguration': list of strings} + host = str([s for s in config_list if "host" in s][0].split('=')[1]) + dbname = str([s for s in config_list if "dbname" in s][0].split('=')[1]) + port = str([s for s in config_list if "port" in s][0].split('=')[1]) + user = str([s for s in config_list if "user" in s][0].split('=')[1]) + pw = str([s for s in config_list if "password" in s][0].split('=')[1]) + return host,dbname,port,user,pw + + def connect_to_archiving_db(self): + """ + Returns a session to a MySQL DBMS using default credentials. + """ + host,dbname,port,user,pw = self.get_db_credentials() + engine = create_engine('mysql+pymysql://'+user+':'+pw+'@'+host+':'+port+'/'+dbname) + Session = sessionmaker(bind=engine) + return Session() + + def get_all_archived_attributes(self): + """ + Returns a list of the archived attributes in the DB. + """ + attrs = self.session.query(Attribute).order_by(Attribute.att_conf_id).all() + # Returns the representation as set in __repr__ method of the mapper class + return attrs + + def get_archived_attributes_by_device(self,device_fqname: str): + """ + Takes as input the fully-qualified name of a device and returns a list of its archived attributes + """ + try: + [domain, family, member] = device_fqname.split('/') + except: + raise AttributeFormatException(f"Could not parse device name {device_fqname}. Please provide FQDN, e.g. STAT/Device/1") + attrs = self.session.query(Attribute).filter(and_(Attribute.domain == domain, Attribute.family == family, \ + Attribute.member == member)).all() + # Returns the representation as set in __repr__ method of the mapper class + return attrs + + def get_attribute_id(self,attribute_fqname: str): + """ + Takes as input the fully-qualified name of an attribute and returns its id. + """ + try: + [domain, family, member, name] = attribute_fqname.split('/') + except: + raise AttributeFormatException(f"Could not parse attribute name {attribute_fqname}. Please provide FQDN, e.g. STAT/Device/1/Attribute") + try: + result = self.session.query(Attribute.att_conf_id).filter(and_(Attribute.domain == domain, Attribute.family == family, \ + Attribute.member == member, Attribute.name == name)).one() + return result[0] + except TypeError as e: + raise Exception("Attribute not found!") from e + except NoResultFound as e: + raise Exception(f"No records of attribute {attribute_fqname} found in DB") from e + + def get_attribute_datatype(self,attribute_fqname: str): + """ + Takes as input the fully-qualified name of an attribute and returns its Data-Type. + Data Type name indicates the type (e.g. string, int, ...) and the read/write property. The name is used + as DB table name suffix in which values are stored. + """ + try: + [domain, family, member, name] = attribute_fqname.split('/') + except: + raise AttributeFormatException(f"Could not parse attribute name {attribute_fqname}. Please provide FQDN, e.g. STAT/Device/1/Attribute") + try: + result = self.session.query(DataType.data_type).join(Attribute,Attribute.att_conf_data_type_id==DataType.att_conf_data_type_id).\ + filter(and_(Attribute.domain == domain, Attribute.family == family, Attribute.member == member, Attribute.name == name)).one() + return result[0] + except TypeError as e: + raise Exception("Attribute not found!") from e + except NoResultFound as e: + raise Exception(f"No records of attribute {attribute_fqname} found in DB") from e + + def get_attribute_value_by_hours(self,attribute_fqname: str, hours: float = 1.0): + """ + Takes as input the attribute fully-qualified name and the number of past hours since the actual time + (e.g. hours=1 retrieves values in the last hour, hours=8.5 retrieves values in the last eight hours and half). + Returns a list of timestamps and a list of values + """ + attr_id = self.get_attribute_id(attribute_fqname) + attr_datatype = self.get_attribute_datatype(attribute_fqname) + attr_table_name = 'att_'+str(attr_datatype) + # Retrieves the class that maps the DB table given the tablename + base_class = get_class_by_tablename(attr_table_name) + # Retrieves the timestamp + time_now = datetime.now() + time_delta = time_now - timedelta(hours=hours) + # Converts the timestamps in the right format for the query + time_now_db = str(time_now.strftime("%Y-%m-%d %X")) + time_delta_db = str(time_delta.strftime("%Y-%m-%d %X")) + try: + result = self.session.query(base_class).\ + join(Attribute,Attribute.att_conf_id==base_class.att_conf_id).\ + filter(and_(Attribute.att_conf_id == attr_id,base_class.data_time >= time_delta_db, \ + base_class.data_time <= time_now_db)).order_by(base_class.data_time).all() + except AttributeError as e: + raise Exception(f"Empty result! Attribute {attribute_fqname} not found") from e + return result + + def get_attribute_value_by_interval(self,attribute_fqname: str, start_time: datetime, stop_time: datetime): + ''' + Takes as input the attribute name and a certain starting and ending point-time. + The datetime format is pretty flexible (e.g. "YYYY-MM-dd hh:mm:ss"). + Returns a list of timestamps and a list of values + ''' + attr_id = self.get_attribute_id(attribute_fqname) + attr_datatype = self.get_attribute_datatype(attribute_fqname) + attr_table_name = 'att_'+str(attr_datatype) + # Retrieves the class that maps the DB table given the tablename + base_class = get_class_by_tablename(attr_table_name) + try: + result = self.session.query(base_class).\ + join(Attribute,Attribute.att_conf_id==base_class.att_conf_id).\ + filter(and_(Attribute.att_conf_id == attr_id,base_class.data_time >= str(start_time), \ + base_class.data_time <= str(stop_time))).order_by(base_class.data_time).all() + except AttributeError as e: + raise Exception(f"Empty result! Attribute {attribute_fqname} not found") from e + return result + diff --git a/devices/toolkit/archiver_base.py b/tangostationcontrol/tangostationcontrol/toolkit/archiver_base.py similarity index 78% rename from devices/toolkit/archiver_base.py rename to tangostationcontrol/tangostationcontrol/toolkit/archiver_base.py index 809b74b2d2f5d96517514a21e9e07cd6a20ef841..1b63db5f5389a00e56decc88f901c5b0ee461f01 100644 --- a/devices/toolkit/archiver_base.py +++ b/tangostationcontrol/tangostationcontrol/toolkit/archiver_base.py @@ -77,7 +77,7 @@ class Scalar_Boolean(Scalar): class Scalar_Boolean_RO(Scalar_Boolean): """ - Class that represents a Tango Scalar Read-Only Value mapped to table 'att_scalar_devdouble_ro' + Class that represents a Tango Scalar Read-Only Value mapped to table 'att_scalar_devboolean_ro' """ __tablename__ = 'att_scalar_devboolean_ro' __table_args__ = {'extend_existing': True} @@ -88,7 +88,7 @@ class Scalar_Boolean_RO(Scalar_Boolean): class Scalar_Boolean_RW(Scalar_Boolean): """ - Class that represents a Tango Scalar Read-Write Value mapped to table 'att_scalar_devdouble_rw' + Class that represents a Tango Scalar Read-Write Value mapped to table 'att_scalar_devboolean_rw' """ __tablename__ = 'att_scalar_devboolean_rw' __table_args__ = {'extend_existing': True} @@ -98,6 +98,38 @@ class Scalar_Boolean_RW(Scalar_Boolean): return "<Scalar_Boolean_RW(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',value_r='%s',value_w='%s',quality='%s',att_error_desc_id='%s')>" \ % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.value_r,self.value_w,self.quality,self.att_error_desc_id) +class Scalar_UChar(Scalar): + """ + Abstract class that represents Parent class of Scalar Uchar mapper classes + """ + # In the concrete inheritance use case, it is common that the base class is not represented + # within the database, only the subclasses. In other words, the base class is abstract. + __abstract__ = True + value_r = Column(Integer) + +class Scalar_UChar_RO(Scalar_UChar): + """ + Class that represents a Tango Scalar Read-Only Value mapped to table 'att_scalar_devuchar_ro' + """ + __tablename__ = 'att_scalar_devuchar_ro' + __table_args__ = {'extend_existing': True} + + def __repr__(self): + return "<Scalar_UChar_RO(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',value_r='%s',quality='%s',att_error_desc_id='%s')>" \ + % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.value_r,self.quality,self.att_error_desc_id) + +class Scalar_UChar_RW(Scalar_UChar): + """ + Class that represents a Tango Scalar Read-Write Value mapped to table 'att_scalar_devuchar_rw' + """ + __tablename__ = 'att_scalar_devuchar_rw' + __table_args__ = {'extend_existing': True} + value_w = Column(Integer) + + def __repr__(self): + return "<Scalar_UChar_RW(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',value_r='%s',value_w='%s',quality='%s',att_error_desc_id='%s')>" \ + % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.value_r,self.value_w,self.quality,self.att_error_desc_id) + class Scalar_Double(Scalar): """ Abstract class that represents Parent class of Scalar Double mapper classes @@ -258,6 +290,39 @@ class Scalar_Long64_RW(Scalar_Long64): return "<Scalar_Long64_RW(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',value_r='%s',value_w='%s',quality='%s',att_error_desc_id='%s')>" \ % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.value_r,self.value_w,self.quality,self.att_error_desc_id) +class Scalar_ULong64(Scalar): + """ + Abstract class that represents Parent class of Scalar ULong64 mapper classes + """ + # In the concrete inheritance use case, it is common that the base class is not represented + # within the database, only the subclasses. In other words, the base class is abstract. + __abstract__ = True + value_r = Column(INTEGER) + +class Scalar_ULong64_RO(Scalar_ULong64): + """ + Class that represents a Tango Scalar Read-Only Value mapped to table 'att_scalar_devulong64_ro' + """ + __tablename__ = 'att_scalar_devulong64_ro' + __table_args__ = {'extend_existing': True} + + def __repr__(self): + return "<Scalar_ULong64_RO(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',value_r='%s',quality='%s',att_error_desc_id='%s')>" \ + % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.value_r,self.quality,self.att_error_desc_id) + +class Scalar_ULong64_RW(Scalar_ULong64): + """ + Class that represents a Tango Scalar Read-Write Value mapped to table 'att_scalar_devulong64_rw' + """ + __tablename__ = 'att_scalar_devulong64_rw' + __table_args__ = {'extend_existing': True} + value_w = Column(INTEGER) + + def __repr__(self): + return "<Scalar_ULong64_RW(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',value_r='%s',value_w='%s',quality='%s',att_error_desc_id='%s')>" \ + % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.value_r,self.value_w,self.quality,self.att_error_desc_id) + + class Scalar_Long(Scalar): """ Abstract class that represents Parent class of Scalar Long mapper classes @@ -290,6 +355,38 @@ class Scalar_Long_RW(Scalar_Long): return "<Scalar_Long_RW(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',value_r='%s',value_w='%s',quality='%s',att_error_desc_id='%s')>" \ % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.value_r,self.value_w,self.quality,self.att_error_desc_id) +class Scalar_ULong(Scalar): + """ + Abstract class that represents Parent class of Scalar ULong mapper classes + """ + # In the concrete inheritance use case, it is common that the base class is not represented + # within the database, only the subclasses. In other words, the base class is abstract. + __abstract__ = True + value_r = Column(INTEGER) + +class Scalar_ULong_RO(Scalar_ULong): + """ + Class that represents a Tango Scalar Read-Only Value mapped to table 'att_scalar_devulong_ro' + """ + __tablename__ = 'att_scalar_devulong_ro' + __table_args__ = {'extend_existing': True} + + def __repr__(self): + return "<Scalar_ULong_RO(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',value_r='%s',quality='%s',att_error_desc_id='%s')>" \ + % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.value_r,self.quality,self.att_error_desc_id) + +class Scalar_ULong_RW(Scalar_ULong): + """ + Class that represents a Tango Scalar Read-Write Value mapped to table 'att_scalar_devulong_rw' + """ + __tablename__ = 'att_scalar_devulong_rw' + __table_args__ = {'extend_existing': True} + value_w = Column(INTEGER) + + def __repr__(self): + return "<Scalar_ULong_RW(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',value_r='%s',value_w='%s',quality='%s',att_error_desc_id='%s')>" \ + % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.value_r,self.value_w,self.quality,self.att_error_desc_id) + class Scalar_Short(Scalar): """ Abstract class that represents Parent class of Scalar Short mapper classes @@ -438,6 +535,40 @@ class Array_Boolean_RW(Array_Boolean): return "<Array_Boolean_RW(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',idx='%s',dim_x_r='%s',dim_y_r='%s',value_r='%s',dim_x_w='%s',dim_y_w='%s',value_w='%s',quality='%s',att_error_desc_id='%s')>" \ % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.idx,self.dim_x_r,self.dim_y_r,self.value_r,self.dim_x_w,self.dim_y_w,self.value_w,self.quality,self.att_error_desc_id) +class Array_UChar(Array): + """ + Abstract class that represents Parent class of Array UChar mapper classes + """ + # In the concrete inheritance use case, it is common that the base class is not represented + # within the database, only the subclasses. In other words, the base class is abstract. + __abstract__ = True + value_r = Column(Integer) + +class Array_UChar_RO(Array_UChar): + """ + Class that represents a Tango Array Read-Only Value mapped to table 'att_array_devuchar_ro' + """ + __tablename__ = 'att_array_devuchar_ro' + __table_args__ = {'extend_existing': True} + + def __repr__(self): + return "<Array_UChar_RO(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',idx='%s',dim_x_r='%s',dim_y_r='%s',value_r='%s',quality='%s',att_error_desc_id='%s')>" \ + % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.idx,self.dim_x_r,self.dim_y_r,self.value_r,self.quality,self.att_error_desc_id) + +class Array_UChar_RW(Array_Boolean): + """ + Class that represents a Tango Array Read-Write Value mapped to table 'att_array_devuchar_rw' + """ + __tablename__ = 'att_array_devuchar_rw' + __table_args__ = {'extend_existing': True} + dim_x_w = Column(Integer) + dim_y_w = Column(Integer) + value_w = Column(Integer) + + def __repr__(self): + return "<Array_UChar_RW(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',idx='%s',dim_x_r='%s',dim_y_r='%s',value_r='%s',dim_x_w='%s',dim_y_w='%s',value_w='%s',quality='%s',att_error_desc_id='%s')>" \ + % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.idx,self.dim_x_r,self.dim_y_r,self.value_r,self.dim_x_w,self.dim_y_w,self.value_w,self.quality,self.att_error_desc_id) + class Array_Double(Array): """ Abstract class that represents Parent class of Array Double mapper classes @@ -608,6 +739,41 @@ class Array_Long64_RW(Array_Long64): return "<Array_Long64_RW(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',idx='%s',dim_x_r='%s',dim_y_r='%s',value_r='%s',dim_x_w='%s',dim_y_w='%s',value_w='%s',quality='%s',att_error_desc_id='%s')>" \ % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.idx,self.dim_x_r,self.dim_y_r,self.value_r,self.dim_x_w,self.dim_y_w,self.value_w,self.quality,self.att_error_desc_id) +class Array_ULong64(Array): + """ + Abstract class that represents Parent class of Array ULong64 mapper classes + """ + # In the concrete inheritance use case, it is common that the base class is not represented + # within the database, only the subclasses. In other words, the base class is abstract. + __abstract__ = True + value_r = Column(INTEGER) + +class Array_ULong64_RO(Array_ULong64): + """ + Class that represents a Tango Array Read-Only Value mapped to table 'att_array_devulong64_ro' + """ + __tablename__ = 'att_array_devulong64_ro' + __table_args__ = {'extend_existing': True} + + def __repr__(self): + return "<Array_ULong64_RO(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',idx='%s',dim_x_r='%s',dim_y_r='%s',value_r='%s',quality='%s',att_error_desc_id='%s')>" \ + % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.idx,self.dim_x_r,self.dim_y_r,self.value_r,self.quality,self.att_error_desc_id) + +class Array_ULong64_RW(Array_ULong64): + """ + Class that represents a Tango Array Read-Write Value mapped to table 'att_array_devulong64_rw' + """ + __tablename__ = 'att_array_devulong64_rw' + __table_args__ = {'extend_existing': True} + dim_x_w = Column(Integer) + dim_y_w = Column(Integer) + value_w = Column(INTEGER) + + def __repr__(self): + return "<Array_ULong64_RW(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',idx='%s',dim_x_r='%s',dim_y_r='%s',value_r='%s',dim_x_w='%s',dim_y_w='%s',value_w='%s',quality='%s',att_error_desc_id='%s')>" \ + % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.idx,self.dim_x_r,self.dim_y_r,self.value_r,self.dim_x_w,self.dim_y_w,self.value_w,self.quality,self.att_error_desc_id) + + class Array_Long(Array): """ Abstract class that represents Parent class of Array Long mapper classes @@ -642,6 +808,40 @@ class Array_Long_RW(Array_Long): return "<Array_Long_RW(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',idx='%s',dim_x_r='%s',dim_y_r='%s',value_r='%s',dim_x_w='%s',dim_y_w='%s',value_w='%s',quality='%s',att_error_desc_id='%s')>" \ % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.idx,self.dim_x_r,self.dim_y_r,self.value_r,self.dim_x_w,self.dim_y_w,self.value_w,self.quality,self.att_error_desc_id) +class Array_ULong(Array): + """ + Abstract class that represents Parent class of Array ULong mapper classes + """ + # In the concrete inheritance use case, it is common that the base class is not represented + # within the database, only the subclasses. In other words, the base class is abstract. + __abstract__ = True + value_r = Column(INTEGER) + +class Array_ULong_RO(Array_ULong): + """ + Class that represents a Tango Array Read-Only Value mapped to table 'att_array_devulong_ro' + """ + __tablename__ = 'att_array_devulong_ro' + __table_args__ = {'extend_existing': True} + + def __repr__(self): + return "<Array_ULong_RO(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',idx='%s',dim_x_r='%s',dim_y_r='%s',value_r='%s',quality='%s',att_error_desc_id='%s')>" \ + % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.idx,self.dim_x_r,self.dim_y_r,self.value_r,self.quality,self.att_error_desc_id) + +class Array_ULong_RW(Array_ULong): + """ + Class that represents a Tango Array Read-Write Value mapped to table 'att_array_devulong_rw' + """ + __tablename__ = 'att_array_devulong_rw' + __table_args__ = {'extend_existing': True} + dim_x_w = Column(Integer) + dim_y_w = Column(Integer) + value_w = Column(INTEGER) + + def __repr__(self): + return "<Array_ULong_RW(att_conf_id='%s',data_time='%s',recv_time='%s',insert_time='%s',idx='%s',dim_x_r='%s',dim_y_r='%s',value_r='%s',dim_x_w='%s',dim_y_w='%s',value_w='%s',quality='%s',att_error_desc_id='%s')>" \ + % (self.att_conf_id,self.data_time,self.recv_time,self.insert_time,self.idx,self.dim_x_r,self.dim_y_r,self.value_r,self.dim_x_w,self.dim_y_w,self.value_w,self.quality,self.att_error_desc_id) + class Array_Short(Array): """ Abstract class that represents Parent class of Array Short mapper classes diff --git a/tangostationcontrol/tangostationcontrol/toolkit/archiver_config/lofar2.json b/tangostationcontrol/tangostationcontrol/toolkit/archiver_config/lofar2.json new file mode 100644 index 0000000000000000000000000000000000000000..f46dae36f4630fa151cf49ce50a4f9b34694be66 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/toolkit/archiver_config/lofar2.json @@ -0,0 +1,33 @@ +{ + "global_variables": { + "development_polling_time": "10000", + "development_archive_time": "60000" + }, + "devices":{ + "STAT/RECV/1": { + "environment": "development", + "include": ["rcu_temperature_r"], + "exclude": ["CLK_Enable_PWR_R","CLK_I2C_STATUS_R","CLK_PLL_error_R","CLK_PLL_locked_R","CLK_translator_busy_R"] + }, + "STAT/SDP/1": { + "environment": "development", + "include": [], + "exclude": [] + }, + "STAT/SST/1": { + "environment": "development", + "include": [], + "exclude": [] + }, + "STAT/XST/1": { + "environment": "development", + "include": [], + "exclude": [] + }, + "STAT/UNB2/1": { + "environment": "development", + "include": [], + "exclude": [] + } + } +} diff --git a/devices/toolkit/attribute_polling_stats.py b/tangostationcontrol/tangostationcontrol/toolkit/attribute_polling_stats.py similarity index 100% rename from devices/toolkit/attribute_polling_stats.py rename to tangostationcontrol/tangostationcontrol/toolkit/attribute_polling_stats.py diff --git a/devices/toolkit/get_internal_attribute_history.py b/tangostationcontrol/tangostationcontrol/toolkit/get_internal_attribute_history.py similarity index 99% rename from devices/toolkit/get_internal_attribute_history.py rename to tangostationcontrol/tangostationcontrol/toolkit/get_internal_attribute_history.py index 735be01613611d73f39678f6b0ed5fb1f1c56a70..8b6b4254f6c10dc8207232b16fd5e22d44e7c556 100644 --- a/devices/toolkit/get_internal_attribute_history.py +++ b/tangostationcontrol/tangostationcontrol/toolkit/get_internal_attribute_history.py @@ -1,10 +1,8 @@ #! /usr/bin/env python3 - from tango import DeviceProxy from numpy import array, transpose - def get_internal_attribute_history(device: DeviceProxy, attribute_name: str, depth: int = 10): try: history = array(device.attribute_history(attr_name = attribute_name, depth = depth)) diff --git a/devices/toolkit/lofar2_config.py b/tangostationcontrol/tangostationcontrol/toolkit/lofar2_config.py similarity index 99% rename from devices/toolkit/lofar2_config.py rename to tangostationcontrol/tangostationcontrol/toolkit/lofar2_config.py index 581eea4f73a4d276613123ec9bf86bdb7e97a0ea..811f4f27abbbac832c6de6811a223a27229e9032 100644 --- a/devices/toolkit/lofar2_config.py +++ b/tangostationcontrol/tangostationcontrol/toolkit/lofar2_config.py @@ -2,7 +2,6 @@ import logging - def configure_logging(): # Always also log the hostname because it makes the origin of the log clear. import socket diff --git a/devices/toolkit/udp_simulator.py b/tangostationcontrol/tangostationcontrol/toolkit/udp_simulator.py similarity index 100% rename from devices/toolkit/udp_simulator.py rename to tangostationcontrol/tangostationcontrol/toolkit/udp_simulator.py diff --git a/devices/test-requirements.txt b/tangostationcontrol/test-requirements.txt similarity index 77% rename from devices/test-requirements.txt rename to tangostationcontrol/test-requirements.txt index c97375e938b0466da884581c339f2c5735472c62..1cd8ccb799fd1dc8b3b25db9051cb12d42d63bb3 100644 --- a/devices/test-requirements.txt +++ b/tangostationcontrol/test-requirements.txt @@ -2,14 +2,18 @@ # order of appearance. Changing the order has an impact on the overall # integration process, which may cause wedges in the gate later. +asynctest>=0.13.0 # Apache-2.0 +bandit>=1.6.0 # Apache-2.0 +coverage>=5.2.0 # Apache-2.0 doc8>=0.8.0 # Apache-2.0 flake8>=3.8.0 # MIT -bandit>=1.6.0 # Apache-2.0 +flake8-breakpoint>=1.1.0 # MIT +flake8-debugger>=4.0.0 #MIT +flake8-mock>=0.3 #GPL hacking>=3.2.0,<3.3.0 # Apache-2.0 -coverage>=5.2.0 # Apache-2.0 python-subunit>=1.4.0 # Apache-2.0/BSD Pygments>=2.6.0 stestr>=3.0.0 # Apache-2.0 testscenarios>=0.5.0 # Apache-2.0/BSD testtools>=2.4.0 # MIT - +timeout-decorator>=0.5 # MIT diff --git a/devices/tox.ini b/tangostationcontrol/tox.ini similarity index 69% rename from devices/tox.ini rename to tangostationcontrol/tox.ini index 18c6cda38751d7bc447e8fb23d92e63b64288ddb..69782d5e33c1ab14ce9abcd50253e0db9eabdf9a 100644 --- a/devices/tox.ini +++ b/tangostationcontrol/tox.ini @@ -13,10 +13,19 @@ setenv = OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 -deps = -r{toxinidir}/test-requirements.txt +deps = + -r{toxinidir}/test-requirements.txt -r{toxinidir}/../docker-compose/lofar-device-base/lofar-requirements.txt commands = stestr run {posargs} +[testenv:integration] +; Warning running integration tests will make changes to your docker system! +; These tests should only be run by the integration-test docker container. +passenv = TANGO_HOST +setenv = TESTS_DIR=./tangostationcontrol/integration_test +commands = + stestr run --serial {posargs} + ; TODO(Corne): Integrate Hacking to customize pep8 rules [testenv:pep8] commands = @@ -30,9 +39,9 @@ commands = ; It thus matters what interfaces Docker will bind our ; containers to, not what our containers listen on. commands = - bandit -r devices/ clients/ common/ examples/ util/ -n5 -ll -s B104 + bandit -r devices/ -n5 -ll -s B104 [flake8] filename = *.py,.stestr.conf,.txt -select = W292 +select = W292,B601,B602,T100,M001 exclude=.tox,.egg-info