diff --git a/config/config.yaml b/config/config.yaml index edf7a303..3dcd82eb 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -1,49 +1,52 @@ SOFTWARE: Small Radio Telescope STATION: - latitude: 42.5 - longitude: -71.5 - name: Haystack + latitude: 42.360236 + longitude: -71.089694 + name: W1XM EMERGENCY_CONTACT: - name: John Doe - email: test@example.com - phone_number: 555-555-5555 + name: Oliver Trevor + email: olt@mit.edu + phone_number: 925-367-3607 AZLIMITS: - lower_bound: 38.0 - upper_bound: 355.0 + lower_bound: 0.0 + upper_bound: 360.0 ELLIMITS: lower_bound: 0.0 - upper_bound: 89.0 + upper_bound: 87.0 STOW_LOCATION: - azimuth: 38.0 - elevation: 0.0 + azimuth: 60.0 + elevation: 30.0 CAL_LOCATION: - azimuth: 120.0 - elevation: 7.0 + azimuth: 270.0 + elevation: 50.0 HORIZON_POINTS: - azimuth: 0 elevation: 0 - azimuth: 120 - elevation: 5 + elevation: 0 - azimuth: 240 - elevation: 5 + elevation: 0 - azimuth: 360 elevation: 0 -MOTOR_TYPE: NONE -MOTOR_PORT: /dev/ttyUSB0 -MOTOR_BAUDRATE: 600 +MOTOR_TYPE: W1XMBIGDISH +MOTOR_PORT: None +MOTOR_BAUDRATE: 0 RADIO_CF: 1420000000 -RADIO_SF: 2400000 -RADIO_FREQ_CORR: -50000 -RADIO_NUM_BINS: 256 -RADIO_INTEG_CYCLES: 100000 +RADIO_SF: 2000000 +RADIO_FREQ_CORR: 00000 +RADIO_RF_GAIN: 20 +RADIO_NUM_BINS: 512 +RADIO_INTEG_CYCLES: 8192 RADIO_AUTOSTART: Yes -NUM_BEAMSWITCHES: 25 -BEAMWIDTH: 7.0 -TSYS: 171 -TCAL: 290 -SAVE_DIRECTORY: ~/Desktop/SRT-Saves +NUM_BEAMSWITCHES: 10 +BEAMWIDTH: 2.9 +CAL_TYPE: NOISE_DIODE +CAL_INTEGRATION_CYCLES: 5 +TSYS: 60.0 +TCAL: 48.5 +SAVE_DIRECTORY: /data/jlab RUN_HEADLESS: No DASHBOARD_PORT: 8080 DASHBOARD_HOST: 0.0.0.0 DASHBOARD_DOWNLOADS: Yes -DASHBOARD_REFRESH_MS: 3000 +DASHBOARD_REFRESH_MS: 500 diff --git a/config/schema.yaml b/config/schema.yaml index 779ad0d9..1fe7be41 100644 --- a/config/schema.yaml +++ b/config/schema.yaml @@ -6,17 +6,20 @@ ELLIMITS: include('limit') STOW_LOCATION: include('az_el_point') CAL_LOCATION: include('az_el_point') HORIZON_POINTS: list(include('az_el_point'), min=0) -MOTOR_TYPE: enum('ALFASPID', 'H180MOUNT', 'PUSHROD', 'NONE') +MOTOR_TYPE: enum('ALFASPID', 'H180MOUNT', 'PUSHROD', 'NONE', 'W1XMBIGDISH') MOTOR_BAUDRATE: int() MOTOR_PORT: str() RADIO_CF: int() RADIO_SF: int() +RADIO_RF_GAIN: int() RADIO_FREQ_CORR: int() RADIO_NUM_BINS: int() RADIO_INTEG_CYCLES: int() RADIO_AUTOSTART: bool() NUM_BEAMSWITCHES: int() BEAMWIDTH: num() +CAL_TYPE: enum('COLD_SKY', 'NOISE_DIODE') +CAL_INTEGRATION_CYCLES: int() TSYS: num() TCAL: num() SAVE_DIRECTORY: str() diff --git a/config/sky_coords.csv b/config/sky_coords.csv index 7c7a7d5e..b8e15d47 100644 --- a/config/sky_coords.csv +++ b/config/sky_coords.csv @@ -6,6 +6,7 @@ fk4,23 21 12,58 44 00,Cass fk4,17 42 54,-28 50 00,SgrA fk4,06 29 12,04 57 00,Rosett fk4,18 17 30,-16 18 00,M17 +fk4,19 57 44,40 35 46,CygA fk4,20 27 00,41 00 00,CygEMN fk4,21 12 00,48 00 00,G90 fk4,05 40 00,29 00 00,G180 @@ -50,4 +51,4 @@ galactic,310,0,G310 galactic,320,0,G320 galactic,330,0,G330 galactic,340,0,G340 -galactic,350,0,G350 \ No newline at end of file +galactic,350,0,G350 diff --git a/docs/command_files.md b/docs/command_files.md index a0b02a7e..706af056 100644 --- a/docs/command_files.md +++ b/docs/command_files.md @@ -8,28 +8,38 @@ The SRT software accepts commands in order to change settings at runtime as well | Command | Parameters | Notes |Info | |--------------|------------|-------|--------------------------------------------| | * | Any text | 1 | Makes Line a Comment | -| stow | None | | Sends the Antenna to Stow | +| azel | [az] [el] | | Points at Azimuth 'az', Elevation 'el' | +| galactic | [l] [b]| | Points at galactic coordinates and tracks | +| radec | [ra] [dec] | 7 | Points at ICRS coordinates and tracks | | cal | None | | Sends the Antenna to Calibration Position | -| calibrate | None | 2 | Saves Current Spec as Calibration Data | +| calibrate | None | 2 | Runs antenna calibration routine | +| calon | None | 6 | Enable calibration noise source | +| caloff | None | 6 | Disable calibration noise source | +| freq | [cf] | | Sets Center Frequency in MHz to 'cf' | +| n | None | | Run an N point scan and display results | +| npointset | [n] | | set number of points to a grid side in n point scan | | quit | None | 3 | Stows and Gracefully Closes Daemon | +| rf_gain | [dB] | | Set Rf gain in SDR | | record | [filename] | 4 | Starts Saving into File of Name 'filename' | | roff | None | | Ends Current Recording if Applicable | -| azel | [az] [el] | | Points at Azimuth 'az', Elevation 'el' | | offset | [az] [el] | | Offsets from Current Object by 'az', 'el' | -| freq | [cf] | | Sets Center Frequency in MHz to 'cf' | | samp | [sf] | | Sets Sampling Frequency in MHz to 'sf' | +| stow | None | | Sends the Antenna to Stow | | wait | [time] | | Stops Execution and Waits for 'time' Secs. | | [time] | None | | Waits for 'time' Seconds | -| LST:hh:mm:ss | None | | Waits Until Next Time hh:mm:ss in UTC | -| Y:D:H:M:S | None | | Waits Until Year:DayOfYear:Hour:Minute:Sec | +| wait_utc_hms | hh:mm:ss | | Waits Until Next Time hh:mm:ss in UTC | +| wait_utc_iso | Y-m-dTH:M:S.f| 8 | Waits Until Year-Month-DayTHour:Minute:Sec | | [name] | [n] or [b] | 5 | Points Antenna at Object named 'name' | Additional Notes: 1. Only is considered a comment if the line starts with '\*'. - 2. Calibration takes samples at its current location (which should typically be 'cal') and uses those to determine the shape of the band-pass filter and eliminate it from the calibrated spectra. Calibration should therefore by done against a non-source object of known noise temperature. + 2. Calibration takes samples at its current location (which should typically be 'cal') and uses those to determine the shape of the band-pass filter and eliminate it from the calibrated spectra. Calibration should therefore by done against a non-source object of known noise temperature. The exat behavior is dependent on the type of calibrator programmed in the config. 3. Unlike the previous SRT software, 'quit' both stows and quits (ends the daemon process). After this is done, it will be necessary to restart the daemon process from command line or via the Dashboard 'Start Daemon' button. 4. Currently, three different file types are supported, and the type used is determined by the file extension of the name given. FITS (Calibrated Spectra) is used when the file ends in '.fits', rad (Calibrated Spectra) is used when it ends in '.rad', and Digital RF (Raw I/Q Samples) is used when the name lacks a file ending. If no filename is provided, Digital RF will be used with an auto-generated name. In order to use rad or FITS with an autogenerated name, use "\*.rad" or "\*.fits" respectively. 5. The names used for pointing at objects are set by the 'sky_coords.csv' file, which is further documented in the config folder portion of the docs. By default, 'Sun' and 'Moon' are already loaded. + 6. calon and caloff commands are valid only for antennas with some form of noise injection or dicke switching. + 7. ICRS coordinates, expects decimal degrees for both Ra and Dec + 8. will accept standard iso formatted utc timestamp to any precision ##### Building Command Files diff --git a/radio/radio_process/radio_process.grc b/radio/radio_process/radio_process.grc index 8ae67250..8ddb734b 100644 --- a/radio/radio_process/radio_process.grc +++ b/radio/radio_process/radio_process.grc @@ -45,6 +45,18 @@ blocks: coordinate: [108, 398] rotation: 0 state: true +- name: cal_on + id: variable + parameters: + comment: '' + value: 'False' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [176, 468.0] + rotation: 0 + state: true - name: cal_pwr id: variable parameters: @@ -69,6 +81,18 @@ blocks: coordinate: [12, 594] rotation: 0 state: true +- name: calibrator_mask + id: variable + parameters: + comment: '' + value: '0b000000000011' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [216, 380.0] + rotation: 0 + state: enabled - name: custom_window id: variable parameters: @@ -165,11 +189,35 @@ blocks: coordinate: [100, 268] rotation: 0 state: true +- name: rf_freq + id: variable + parameters: + comment: '' + value: freq + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [984, 12.0] + rotation: 0 + state: enabled +- name: rf_gain + id: variable + parameters: + comment: '' + value: '20' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [896, 12.0] + rotation: 0 + state: enabled - name: samp_rate id: variable parameters: comment: '' - value: '2400000' + value: '2000000' states: bus_sink: false bus_source: false @@ -358,6 +406,7 @@ blocks: maxoutbuf: '0' minoutbuf: '0' num_ports: '1' + showports: 'False' type: complex vlen: '1' states: @@ -377,6 +426,7 @@ blocks: maxoutbuf: '0' minoutbuf: '0' num_ports: '1' + showports: 'False' type: complex vlen: '1' states: @@ -396,6 +446,7 @@ blocks: maxoutbuf: '0' minoutbuf: '0' num_ports: '1' + showports: 'False' type: complex vlen: '1' states: @@ -437,7 +488,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [823, 281] + coordinate: [800, 276.0] rotation: 0 state: true - name: blocks_multiply_const_vxx_0 @@ -695,7 +746,7 @@ blocks: value: 'pmt.to_pmt({"num_bins": num_bins, "samp_rate": samp_rate, "num_integrations": num_integrations, "motor_az": motor_az, "motor_el": motor_el, "freq": freq, "tsys": tsys, "tcal": tcal, "cal_pwr": cal_pwr, "vlsr": vlsr, "glat": glat, - "glon": glon, "soutrack": soutrack, "bsw": beam_switch})' + "glon": glon, "soutrack": soutrack, "bsw": beam_switch, "cal_on":cal_on})' vlen: '1' states: bus_sink: false @@ -704,6 +755,53 @@ blocks: coordinate: [548, 253] rotation: 0 state: true +- name: calibrator_control_strobe + id: epy_block + parameters: + _source_code: "\"\"\"\nblock to generate calibrator control commands for the X300\ + \ radio. \nnote that this block assumes a latency lower than the calibrator\ + \ cycle time\nand will break if that condition is not met\n\nEmbedded Python\ + \ Blocks:\n\nEach time this file is saved, GRC will instantiate the first class\ + \ it finds\nto get ports and parameters of your block. The arguments to __init__\ + \ will\nbe the parameters. All of them are required to have default values!\n\ + \"\"\"\nimport numpy as np\nfrom gnuradio import gr \nimport pmt\nimport time\n\ + import uhd\n\nclass msg_blk(gr.sync_block):\n def __init__(self, calibrator_mask=0xFFF,cal_state=False):\n\ + \ gr.sync_block.__init__(\n self,\n name=\"calibrator_msg_block\"\ + ,\n in_sig=None,\n out_sig=None\n )\n\n #input\ + \ parameters\n self.calibrator_mask = calibrator_mask\n self.cal_state\ + \ = cal_state\n\n #derived variables\n\n self.last_cal_state =\ + \ False\n \n self.message_port_register_in(pmt.intern('strobe'))\n\ + \ self.message_port_register_out(pmt.intern('command'))\n \n \ + \ self.set_msg_handler(pmt.intern('strobe'), self.handle_msg)\n\n def\ + \ handle_msg(self, msg):\n\n #send message to define next calibrator\ + \ state change\n\n if self.last_cal_state != self.cal_state:\n \ + \ if self.cal_state:\n new_cal_value = 0xFFF\n \ + \ else:\n new_cal_value = 0x000\n\n #issue command to\ + \ set toggle gpio\n\n set_gpio = pmt.make_dict()\n set_gpio =\ + \ pmt.dict_add(set_gpio, pmt.to_pmt('bank'), pmt.to_pmt('FP0A'))\n set_gpio\ + \ = pmt.dict_add(set_gpio, pmt.to_pmt('attr'), pmt.to_pmt('OUT'))\n set_gpio\ + \ = pmt.dict_add(set_gpio, pmt.to_pmt('value'), pmt.from_double(new_cal_value))\n\ + \ set_gpio = pmt.dict_add(set_gpio, pmt.to_pmt('mask'), pmt.from_double(self.calibrator_mask))\n\ + \n msg = pmt.make_dict()\n msg = pmt.dict_add(msg, pmt.to_pmt('gpio'),\ + \ set_gpio)\n\n self.message_port_pub(pmt.intern('command'), msg) #issue\ + \ message\n\n self.last_cal_state = self.cal_state\n\n " + affinity: '' + alias: '' + cal_state: cal_on + calibrator_mask: calibrator_mask + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + states: + _io_cache: ('calibrator_msg_block', 'msg_blk', [('calibrator_mask', '4095'), ('cal_state', + 'False')], [('strobe', 'message', 1)], [('command', 'message', 1)], '', ['cal_state', + 'calibrator_mask']) + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1080, 276.0] + rotation: 0 + state: true - name: dc_blocker_xx_0 id: dc_blocker_xx parameters: @@ -1157,7 +1255,466 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [293, 99] + coordinate: [296, 92.0] + rotation: 0 + state: disabled +- name: snippet_0 + id: snippet + parameters: + alias: '' + code: 'calibrator_control_mask = self.calibrator_mask + + self.usrp0.set_gpio_attr(''FP0A'', ''CTRL'', 0x000, 0xFFF ^ calibrator_control_mask) #set + pins 2 and 3 manual + + self.usrp0.set_gpio_attr(''FP0A'', ''DDR'', 0xFFF, calibrator_control_mask) + #set pins 2 and 3 as output + + self.usrp0.set_gpio_attr(''FP0A'', ''OUT'', 0x000 , calibrator_control_mask)' + comment: '' + priority: '0' + section: main_after_init + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [200, 300.0] + rotation: 0 + state: true +- name: uhd_usrp_source_1 + id: uhd_usrp_source + parameters: + affinity: '' + alias: usrp0 + ant0: '"RX2"' + ant1: '"RX2"' + ant10: '"RX2"' + ant11: '"RX2"' + ant12: '"RX2"' + ant13: '"RX2"' + ant14: '"RX2"' + ant15: '"RX2"' + ant16: '"RX2"' + ant17: '"RX2"' + ant18: '"RX2"' + ant19: '"RX2"' + ant2: '"RX2"' + ant20: '"RX2"' + ant21: '"RX2"' + ant22: '"RX2"' + ant23: '"RX2"' + ant24: '"RX2"' + ant25: '"RX2"' + ant26: '"RX2"' + ant27: '"RX2"' + ant28: '"RX2"' + ant29: '"RX2"' + ant3: '"RX2"' + ant30: '"RX2"' + ant31: '"RX2"' + ant4: '"RX2"' + ant5: '"RX2"' + ant6: '"RX2"' + ant7: '"RX2"' + ant8: '"RX2"' + ant9: '"RX2"' + bw0: samp_rate + bw1: '0' + bw10: '0' + bw11: '0' + bw12: '0' + bw13: '0' + bw14: '0' + bw15: '0' + bw16: '0' + bw17: '0' + bw18: '0' + bw19: '0' + bw2: '0' + bw20: '0' + bw21: '0' + bw22: '0' + bw23: '0' + bw24: '0' + bw25: '0' + bw26: '0' + bw27: '0' + bw28: '0' + bw29: '0' + bw3: '0' + bw30: '0' + bw31: '0' + bw4: '0' + bw5: '0' + bw6: '0' + bw7: '0' + bw8: '0' + bw9: '0' + center_freq0: rf_freq + center_freq1: '0' + center_freq10: '0' + center_freq11: '0' + center_freq12: '0' + center_freq13: '0' + center_freq14: '0' + center_freq15: '0' + center_freq16: '0' + center_freq17: '0' + center_freq18: '0' + center_freq19: '0' + center_freq2: '0' + center_freq20: '0' + center_freq21: '0' + center_freq22: '0' + center_freq23: '0' + center_freq24: '0' + center_freq25: '0' + center_freq26: '0' + center_freq27: '0' + center_freq28: '0' + center_freq29: '0' + center_freq3: '0' + center_freq30: '0' + center_freq31: '0' + center_freq4: '0' + center_freq5: '0' + center_freq6: '0' + center_freq7: '0' + center_freq8: '0' + center_freq9: '0' + clock_rate: 0e0 + clock_source0: '' + clock_source1: '' + clock_source2: '' + clock_source3: '' + clock_source4: '' + clock_source5: '' + clock_source6: '' + clock_source7: '' + comment: '' + dc_offs0: 0+0j + dc_offs1: 0+0j + dc_offs10: 0+0j + dc_offs11: 0+0j + dc_offs12: 0+0j + dc_offs13: 0+0j + dc_offs14: 0+0j + dc_offs15: 0+0j + dc_offs16: 0+0j + dc_offs17: 0+0j + dc_offs18: 0+0j + dc_offs19: 0+0j + dc_offs2: 0+0j + dc_offs20: 0+0j + dc_offs21: 0+0j + dc_offs22: 0+0j + dc_offs23: 0+0j + dc_offs24: 0+0j + dc_offs25: 0+0j + dc_offs26: 0+0j + dc_offs27: 0+0j + dc_offs28: 0+0j + dc_offs29: 0+0j + dc_offs3: 0+0j + dc_offs30: 0+0j + dc_offs31: 0+0j + dc_offs4: 0+0j + dc_offs5: 0+0j + dc_offs6: 0+0j + dc_offs7: 0+0j + dc_offs8: 0+0j + dc_offs9: 0+0j + dc_offs_enb0: auto + dc_offs_enb1: default + dc_offs_enb10: default + dc_offs_enb11: default + dc_offs_enb12: default + dc_offs_enb13: default + dc_offs_enb14: default + dc_offs_enb15: default + dc_offs_enb16: default + dc_offs_enb17: default + dc_offs_enb18: default + dc_offs_enb19: default + dc_offs_enb2: default + dc_offs_enb20: default + dc_offs_enb21: default + dc_offs_enb22: default + dc_offs_enb23: default + dc_offs_enb24: default + dc_offs_enb25: default + dc_offs_enb26: default + dc_offs_enb27: default + dc_offs_enb28: default + dc_offs_enb29: default + dc_offs_enb3: default + dc_offs_enb30: default + dc_offs_enb31: default + dc_offs_enb4: default + dc_offs_enb5: default + dc_offs_enb6: default + dc_offs_enb7: default + dc_offs_enb8: default + dc_offs_enb9: default + dev_addr: '"addr=172.25.14.11"' + dev_args: '' + gain0: rf_gain + gain1: '0' + gain10: '0' + gain11: '0' + gain12: '0' + gain13: '0' + gain14: '0' + gain15: '0' + gain16: '0' + gain17: '0' + gain18: '0' + gain19: '0' + gain2: '0' + gain20: '0' + gain21: '0' + gain22: '0' + gain23: '0' + gain24: '0' + gain25: '0' + gain26: '0' + gain27: '0' + gain28: '0' + gain29: '0' + gain3: '0' + gain30: '0' + gain31: '0' + gain4: '0' + gain5: '0' + gain6: '0' + gain7: '0' + gain8: '0' + gain9: '0' + gain_type0: default + gain_type1: default + gain_type10: default + gain_type11: default + gain_type12: default + gain_type13: default + gain_type14: default + gain_type15: default + gain_type16: default + gain_type17: default + gain_type18: default + gain_type19: default + gain_type2: default + gain_type20: default + gain_type21: default + gain_type22: default + gain_type23: default + gain_type24: default + gain_type25: default + gain_type26: default + gain_type27: default + gain_type28: default + gain_type29: default + gain_type3: default + gain_type30: default + gain_type31: default + gain_type4: default + gain_type5: default + gain_type6: default + gain_type7: default + gain_type8: default + gain_type9: default + iq_imbal0: 0+0j + iq_imbal1: 0+0j + iq_imbal10: 0+0j + iq_imbal11: 0+0j + iq_imbal12: 0+0j + iq_imbal13: 0+0j + iq_imbal14: 0+0j + iq_imbal15: 0+0j + iq_imbal16: 0+0j + iq_imbal17: 0+0j + iq_imbal18: 0+0j + iq_imbal19: 0+0j + iq_imbal2: 0+0j + iq_imbal20: 0+0j + iq_imbal21: 0+0j + iq_imbal22: 0+0j + iq_imbal23: 0+0j + iq_imbal24: 0+0j + iq_imbal25: 0+0j + iq_imbal26: 0+0j + iq_imbal27: 0+0j + iq_imbal28: 0+0j + iq_imbal29: 0+0j + iq_imbal3: 0+0j + iq_imbal30: 0+0j + iq_imbal31: 0+0j + iq_imbal4: 0+0j + iq_imbal5: 0+0j + iq_imbal6: 0+0j + iq_imbal7: 0+0j + iq_imbal8: 0+0j + iq_imbal9: 0+0j + iq_imbal_enb0: auto + iq_imbal_enb1: default + iq_imbal_enb10: default + iq_imbal_enb11: default + iq_imbal_enb12: default + iq_imbal_enb13: default + iq_imbal_enb14: default + iq_imbal_enb15: default + iq_imbal_enb16: default + iq_imbal_enb17: default + iq_imbal_enb18: default + iq_imbal_enb19: default + iq_imbal_enb2: default + iq_imbal_enb20: default + iq_imbal_enb21: default + iq_imbal_enb22: default + iq_imbal_enb23: default + iq_imbal_enb24: default + iq_imbal_enb25: default + iq_imbal_enb26: default + iq_imbal_enb27: default + iq_imbal_enb28: default + iq_imbal_enb29: default + iq_imbal_enb3: default + iq_imbal_enb30: default + iq_imbal_enb31: default + iq_imbal_enb4: default + iq_imbal_enb5: default + iq_imbal_enb6: default + iq_imbal_enb7: default + iq_imbal_enb8: default + iq_imbal_enb9: default + lo_export0: 'False' + lo_export1: 'False' + lo_export10: 'False' + lo_export11: 'False' + lo_export12: 'False' + lo_export13: 'False' + lo_export14: 'False' + lo_export15: 'False' + lo_export16: 'False' + lo_export17: 'False' + lo_export18: 'False' + lo_export19: 'False' + lo_export2: 'False' + lo_export20: 'False' + lo_export21: 'False' + lo_export22: 'False' + lo_export23: 'False' + lo_export24: 'False' + lo_export25: 'False' + lo_export26: 'False' + lo_export27: 'False' + lo_export28: 'False' + lo_export29: 'False' + lo_export3: 'False' + lo_export30: 'False' + lo_export31: 'False' + lo_export4: 'False' + lo_export5: 'False' + lo_export6: 'False' + lo_export7: 'False' + lo_export8: 'False' + lo_export9: 'False' + lo_source0: internal + lo_source1: internal + lo_source10: internal + lo_source11: internal + lo_source12: internal + lo_source13: internal + lo_source14: internal + lo_source15: internal + lo_source16: internal + lo_source17: internal + lo_source18: internal + lo_source19: internal + lo_source2: internal + lo_source20: internal + lo_source21: internal + lo_source22: internal + lo_source23: internal + lo_source24: internal + lo_source25: internal + lo_source26: internal + lo_source27: internal + lo_source28: internal + lo_source29: internal + lo_source3: internal + lo_source30: internal + lo_source31: internal + lo_source4: internal + lo_source5: internal + lo_source6: internal + lo_source7: internal + lo_source8: internal + lo_source9: internal + maxoutbuf: '0' + minoutbuf: '0' + nchan: '1' + num_mboards: '1' + otw: '' + rx_agc0: Default + rx_agc1: Default + rx_agc10: Default + rx_agc11: Default + rx_agc12: Default + rx_agc13: Default + rx_agc14: Default + rx_agc15: Default + rx_agc16: Default + rx_agc17: Default + rx_agc18: Default + rx_agc19: Default + rx_agc2: Default + rx_agc20: Default + rx_agc21: Default + rx_agc22: Default + rx_agc23: Default + rx_agc24: Default + rx_agc25: Default + rx_agc26: Default + rx_agc27: Default + rx_agc28: Default + rx_agc29: Default + rx_agc3: Default + rx_agc30: Default + rx_agc31: Default + rx_agc4: Default + rx_agc5: Default + rx_agc6: Default + rx_agc7: Default + rx_agc8: Default + rx_agc9: Default + samp_rate: samp_rate + sd_spec0: '' + sd_spec1: '' + sd_spec2: '' + sd_spec3: '' + sd_spec4: '' + sd_spec5: '' + sd_spec6: '' + sd_spec7: '' + show_lo_controls: 'False' + start_time: '-1.0' + stream_args: '' + stream_chans: '[]' + sync: pc_clock_next_pps + time_source0: '' + time_source1: '' + time_source2: '' + time_source3: '' + time_source4: '' + time_source5: '' + time_source6: '' + time_source7: '' + type: fc32 + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [328, 108.0] rotation: 0 state: true - name: xmlrpc_server_0 @@ -1171,7 +1728,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [935, 52] + coordinate: [1016, 84.0] rotation: 0 state: true - name: zeromq_pub_sink_0 @@ -1180,6 +1737,7 @@ blocks: address: tcp://127.0.0.1:5558 affinity: '' alias: '' + bind: 'True' comment: '' drop_on_hwm: 'True' hwm: '-1' @@ -1201,6 +1759,7 @@ blocks: address: tcp://127.0.0.1:5559 affinity: '' alias: '' + bind: 'True' comment: '' drop_on_hwm: 'True' hwm: '-1' @@ -1222,6 +1781,7 @@ blocks: address: tcp://127.0.0.1:5563 affinity: '' alias: '' + bind: 'True' comment: '' drop_on_hwm: 'True' hwm: '-1' @@ -1243,6 +1803,7 @@ blocks: address: tcp://127.0.0.1:5562 affinity: '' alias: '' + bind: 'True' comment: '' drop_on_hwm: 'True' hwm: '-1' @@ -1264,6 +1825,7 @@ blocks: address: tcp://127.0.0.1:5560 affinity: '' alias: '' + bind: 'True' comment: '' drop_on_hwm: 'True' hwm: '-1' @@ -1285,6 +1847,7 @@ blocks: address: tcp://127.0.0.1:5561 affinity: '' alias: '' + bind: 'True' comment: '' drop_on_hwm: 'True' hwm: '-1' @@ -1310,6 +1873,7 @@ connections: - [blocks_delay_0_0, '0', blocks_stream_to_vector_0_0, '0'] - [blocks_delay_0_1, '0', blocks_stream_to_vector_0_1, '0'] - [blocks_integrate_xx_0, '0', blocks_multiply_const_xx_0, '0'] +- [blocks_message_strobe_0, strobe, calibrator_control_strobe, strobe] - [blocks_multiply_const_vxx_0, '0', blocks_add_xx_0, '0'] - [blocks_multiply_const_vxx_0_0, '0', blocks_add_xx_0, '1'] - [blocks_multiply_const_vxx_0_0_0, '0', blocks_add_xx_0, '2'] @@ -1332,10 +1896,12 @@ connections: - [blocks_stream_to_vector_0_2, '0', blocks_multiply_const_vxx_0_0_0_0, '0'] - [blocks_tags_strobe_0, '0', blocks_add_xx_0_0, '0'] - [blocks_tags_strobe_0_0, '0', blocks_add_xx_0_0, '2'] +- [calibrator_control_strobe, command, uhd_usrp_source_1, command] - [dc_blocker_xx_0, '0', blocks_skiphead_0, '0'] - [fft_vxx_0, '0', blocks_complex_to_mag_squared_0, '0'] - [osmosdr_source_0, '0', add_clock_tags, '0'] +- [uhd_usrp_source_1, '0', add_clock_tags, '0'] metadata: file_format: 1 - grc_version: 3.10.5.1 + grc_version: 3.10.9.2 diff --git a/radio/radio_process/radioonly.grc b/radio/radio_process/radioonly.grc index 0d27718d..3486591e 100644 --- a/radio/radio_process/radioonly.grc +++ b/radio/radio_process/radioonly.grc @@ -165,6 +165,30 @@ blocks: coordinate: [132, 282] rotation: 0 state: true +- name: rf_freq + id: variable + parameters: + comment: '' + value: freq + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1080, 60.0] + rotation: 0 + state: enabled +- name: rf_gain + id: variable + parameters: + comment: '' + value: '20' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1000, 60.0] + rotation: 0 + state: enabled - name: samp_rate id: variable parameters: @@ -1042,7 +1066,7 @@ blocks: bus_structure: null coordinate: [432, 172.0] rotation: 0 - state: true + state: disabled - name: qtgui_sink_x_0 id: qtgui_sink_x parameters: @@ -1294,6 +1318,442 @@ blocks: coordinate: [904, 836.0] rotation: 0 state: true +- name: usrp0 + id: uhd_usrp_source + parameters: + affinity: '' + alias: usrp0 + ant0: '"RX2"' + ant1: '"RX2"' + ant10: '"RX2"' + ant11: '"RX2"' + ant12: '"RX2"' + ant13: '"RX2"' + ant14: '"RX2"' + ant15: '"RX2"' + ant16: '"RX2"' + ant17: '"RX2"' + ant18: '"RX2"' + ant19: '"RX2"' + ant2: '"RX2"' + ant20: '"RX2"' + ant21: '"RX2"' + ant22: '"RX2"' + ant23: '"RX2"' + ant24: '"RX2"' + ant25: '"RX2"' + ant26: '"RX2"' + ant27: '"RX2"' + ant28: '"RX2"' + ant29: '"RX2"' + ant3: '"RX2"' + ant30: '"RX2"' + ant31: '"RX2"' + ant4: '"RX2"' + ant5: '"RX2"' + ant6: '"RX2"' + ant7: '"RX2"' + ant8: '"RX2"' + ant9: '"RX2"' + bw0: samp_rate + bw1: samp_rate + bw10: '0' + bw11: '0' + bw12: '0' + bw13: '0' + bw14: '0' + bw15: '0' + bw16: '0' + bw17: '0' + bw18: '0' + bw19: '0' + bw2: '0' + bw20: '0' + bw21: '0' + bw22: '0' + bw23: '0' + bw24: '0' + bw25: '0' + bw26: '0' + bw27: '0' + bw28: '0' + bw29: '0' + bw3: '0' + bw30: '0' + bw31: '0' + bw4: '0' + bw5: '0' + bw6: '0' + bw7: '0' + bw8: '0' + bw9: '0' + center_freq0: freq + center_freq1: freq + center_freq10: '0' + center_freq11: '0' + center_freq12: '0' + center_freq13: '0' + center_freq14: '0' + center_freq15: '0' + center_freq16: '0' + center_freq17: '0' + center_freq18: '0' + center_freq19: '0' + center_freq2: rf_freq + center_freq20: '0' + center_freq21: '0' + center_freq22: '0' + center_freq23: '0' + center_freq24: '0' + center_freq25: '0' + center_freq26: '0' + center_freq27: '0' + center_freq28: '0' + center_freq29: '0' + center_freq3: rf_freq + center_freq30: '0' + center_freq31: '0' + center_freq4: '0' + center_freq5: '0' + center_freq6: '0' + center_freq7: '0' + center_freq8: '0' + center_freq9: '0' + clock_rate: 0e0 + clock_source0: '' + clock_source1: '' + clock_source2: '' + clock_source3: '' + clock_source4: '' + clock_source5: '' + clock_source6: '' + clock_source7: '' + comment: '' + dc_offs0: 0+0j + dc_offs1: 0+0j + dc_offs10: 0+0j + dc_offs11: 0+0j + dc_offs12: 0+0j + dc_offs13: 0+0j + dc_offs14: 0+0j + dc_offs15: 0+0j + dc_offs16: 0+0j + dc_offs17: 0+0j + dc_offs18: 0+0j + dc_offs19: 0+0j + dc_offs2: 0+0j + dc_offs20: 0+0j + dc_offs21: 0+0j + dc_offs22: 0+0j + dc_offs23: 0+0j + dc_offs24: 0+0j + dc_offs25: 0+0j + dc_offs26: 0+0j + dc_offs27: 0+0j + dc_offs28: 0+0j + dc_offs29: 0+0j + dc_offs3: 0+0j + dc_offs30: 0+0j + dc_offs31: 0+0j + dc_offs4: 0+0j + dc_offs5: 0+0j + dc_offs6: 0+0j + dc_offs7: 0+0j + dc_offs8: 0+0j + dc_offs9: 0+0j + dc_offs_enb0: default + dc_offs_enb1: default + dc_offs_enb10: default + dc_offs_enb11: default + dc_offs_enb12: default + dc_offs_enb13: default + dc_offs_enb14: default + dc_offs_enb15: default + dc_offs_enb16: default + dc_offs_enb17: default + dc_offs_enb18: default + dc_offs_enb19: default + dc_offs_enb2: default + dc_offs_enb20: default + dc_offs_enb21: default + dc_offs_enb22: default + dc_offs_enb23: default + dc_offs_enb24: default + dc_offs_enb25: default + dc_offs_enb26: default + dc_offs_enb27: default + dc_offs_enb28: default + dc_offs_enb29: default + dc_offs_enb3: default + dc_offs_enb30: default + dc_offs_enb31: default + dc_offs_enb4: default + dc_offs_enb5: default + dc_offs_enb6: default + dc_offs_enb7: default + dc_offs_enb8: default + dc_offs_enb9: default + dev_addr: '"addr=172.25.14.11"' + dev_args: '' + gain0: rf_gain + gain1: rf_gain + gain10: '0' + gain11: '0' + gain12: '0' + gain13: '0' + gain14: '0' + gain15: '0' + gain16: '0' + gain17: '0' + gain18: '0' + gain19: '0' + gain2: rf_gain + gain20: '0' + gain21: '0' + gain22: '0' + gain23: '0' + gain24: '0' + gain25: '0' + gain26: '0' + gain27: '0' + gain28: '0' + gain29: '0' + gain3: rf_gain + gain30: '0' + gain31: '0' + gain4: '0' + gain5: '0' + gain6: '0' + gain7: '0' + gain8: '0' + gain9: '0' + gain_type0: default + gain_type1: default + gain_type10: default + gain_type11: default + gain_type12: default + gain_type13: default + gain_type14: default + gain_type15: default + gain_type16: default + gain_type17: default + gain_type18: default + gain_type19: default + gain_type2: default + gain_type20: default + gain_type21: default + gain_type22: default + gain_type23: default + gain_type24: default + gain_type25: default + gain_type26: default + gain_type27: default + gain_type28: default + gain_type29: default + gain_type3: default + gain_type30: default + gain_type31: default + gain_type4: default + gain_type5: default + gain_type6: default + gain_type7: default + gain_type8: default + gain_type9: default + iq_imbal0: 0+0j + iq_imbal1: 0+0j + iq_imbal10: 0+0j + iq_imbal11: 0+0j + iq_imbal12: 0+0j + iq_imbal13: 0+0j + iq_imbal14: 0+0j + iq_imbal15: 0+0j + iq_imbal16: 0+0j + iq_imbal17: 0+0j + iq_imbal18: 0+0j + iq_imbal19: 0+0j + iq_imbal2: 0+0j + iq_imbal20: 0+0j + iq_imbal21: 0+0j + iq_imbal22: 0+0j + iq_imbal23: 0+0j + iq_imbal24: 0+0j + iq_imbal25: 0+0j + iq_imbal26: 0+0j + iq_imbal27: 0+0j + iq_imbal28: 0+0j + iq_imbal29: 0+0j + iq_imbal3: 0+0j + iq_imbal30: 0+0j + iq_imbal31: 0+0j + iq_imbal4: 0+0j + iq_imbal5: 0+0j + iq_imbal6: 0+0j + iq_imbal7: 0+0j + iq_imbal8: 0+0j + iq_imbal9: 0+0j + iq_imbal_enb0: default + iq_imbal_enb1: default + iq_imbal_enb10: default + iq_imbal_enb11: default + iq_imbal_enb12: default + iq_imbal_enb13: default + iq_imbal_enb14: default + iq_imbal_enb15: default + iq_imbal_enb16: default + iq_imbal_enb17: default + iq_imbal_enb18: default + iq_imbal_enb19: default + iq_imbal_enb2: default + iq_imbal_enb20: default + iq_imbal_enb21: default + iq_imbal_enb22: default + iq_imbal_enb23: default + iq_imbal_enb24: default + iq_imbal_enb25: default + iq_imbal_enb26: default + iq_imbal_enb27: default + iq_imbal_enb28: default + iq_imbal_enb29: default + iq_imbal_enb3: default + iq_imbal_enb30: default + iq_imbal_enb31: default + iq_imbal_enb4: default + iq_imbal_enb5: default + iq_imbal_enb6: default + iq_imbal_enb7: default + iq_imbal_enb8: default + iq_imbal_enb9: default + lo_export0: 'False' + lo_export1: 'False' + lo_export10: 'False' + lo_export11: 'False' + lo_export12: 'False' + lo_export13: 'False' + lo_export14: 'False' + lo_export15: 'False' + lo_export16: 'False' + lo_export17: 'False' + lo_export18: 'False' + lo_export19: 'False' + lo_export2: 'False' + lo_export20: 'False' + lo_export21: 'False' + lo_export22: 'False' + lo_export23: 'False' + lo_export24: 'False' + lo_export25: 'False' + lo_export26: 'False' + lo_export27: 'False' + lo_export28: 'False' + lo_export29: 'False' + lo_export3: 'False' + lo_export30: 'False' + lo_export31: 'False' + lo_export4: 'False' + lo_export5: 'False' + lo_export6: 'False' + lo_export7: 'False' + lo_export8: 'False' + lo_export9: 'False' + lo_source0: internal + lo_source1: internal + lo_source10: internal + lo_source11: internal + lo_source12: internal + lo_source13: internal + lo_source14: internal + lo_source15: internal + lo_source16: internal + lo_source17: internal + lo_source18: internal + lo_source19: internal + lo_source2: external + lo_source20: internal + lo_source21: internal + lo_source22: internal + lo_source23: internal + lo_source24: internal + lo_source25: internal + lo_source26: internal + lo_source27: internal + lo_source28: internal + lo_source29: internal + lo_source3: external + lo_source30: internal + lo_source31: internal + lo_source4: internal + lo_source5: internal + lo_source6: internal + lo_source7: internal + lo_source8: internal + lo_source9: internal + maxoutbuf: '0' + minoutbuf: '0' + nchan: '1' + num_mboards: '1' + otw: '' + rx_agc0: Default + rx_agc1: Default + rx_agc10: Default + rx_agc11: Default + rx_agc12: Default + rx_agc13: Default + rx_agc14: Default + rx_agc15: Default + rx_agc16: Default + rx_agc17: Default + rx_agc18: Default + rx_agc19: Default + rx_agc2: Default + rx_agc20: Default + rx_agc21: Default + rx_agc22: Default + rx_agc23: Default + rx_agc24: Default + rx_agc25: Default + rx_agc26: Default + rx_agc27: Default + rx_agc28: Default + rx_agc29: Default + rx_agc3: Default + rx_agc30: Default + rx_agc31: Default + rx_agc4: Default + rx_agc5: Default + rx_agc6: Default + rx_agc7: Default + rx_agc8: Default + rx_agc9: Default + samp_rate: samp_rate + sd_spec0: '' + sd_spec1: '' + sd_spec2: '' + sd_spec3: '' + sd_spec4: '' + sd_spec5: '' + sd_spec6: '' + sd_spec7: '' + show_lo_controls: 'False' + start_time: '-1.0' + stream_args: '' + stream_chans: '[]' + sync: pc_clock_next_pps + time_source0: '' + time_source1: '' + time_source2: '' + time_source3: '' + time_source4: '' + time_source5: '' + time_source6: '' + time_source7: '' + type: fc32 + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [456, 188.0] + rotation: 0 + state: true connections: - [blocks_add_xx_0, '0', fft_vxx_0, '0'] @@ -1322,7 +1782,8 @@ connections: - [fft_vxx_0, '0', blocks_multiply_conjugate_cc_0, '0'] - [fft_vxx_0, '0', blocks_multiply_conjugate_cc_0, '1'] - [osmosdr_source_0, '0', dc_blocker_xx_0, '0'] +- [usrp0, '0', dc_blocker_xx_0, '0'] metadata: file_format: 1 - grc_version: 3.10.5.1 + grc_version: 3.10.5.0 diff --git a/radio/radio_save_raw/radio_save_raw.grc b/radio/radio_save_raw/radio_save_raw.grc index 1648de42..400809b8 100644 --- a/radio/radio_save_raw/radio_save_raw.grc +++ b/radio/radio_save_raw/radio_save_raw.grc @@ -1,6 +1,7 @@ options: parameters: author: '' + catch_exceptions: 'True' category: '[GRC Hier Blocks]' cmake_opt: '' comment: '' @@ -49,17 +50,48 @@ blocks: coordinate: [11, 203] rotation: 0 state: true -- name: gr_digital_rf_digital_rf_channel_sink_0 - id: gr_digital_rf_digital_rf_channel_sink +- name: gr_digital_rf_digital_rf_sink_0 + id: gr_digital_rf_digital_rf_sink parameters: affinity: '' alias: '' center_freqs: '[]' + channel0: ch0 + channel1: ch1 + channel10: ch10 + channel11: ch11 + channel12: ch12 + channel13: ch13 + channel14: ch14 + channel15: ch15 + channel16: ch16 + channel17: ch17 + channel18: ch18 + channel19: ch19 + channel2: ch2 + channel20: ch20 + channel21: ch21 + channel22: ch22 + channel23: ch23 + channel24: ch24 + channel25: ch25 + channel26: ch26 + channel27: ch27 + channel28: ch28 + channel29: ch29 + channel3: ch3 + channel30: ch30 + channel31: ch31 + channel4: ch4 + channel5: ch5 + channel6: ch6 + channel7: ch7 + channel8: ch8 + channel9: ch9 checksum: 'False' comment: '' compression_level: '0' debug: 'False' - dir: directory_name file_cadence_ms: '1000' ignore_tags: 'False' input: fc32 @@ -67,21 +99,23 @@ blocks: marching_periods: 'True' metadata: '{}' min_chunksize: '0' + nchan: '1' sample_rate_denominator: '1' sample_rate_numerator: int(samp_rate) start: '''now''' stop_on_skipped: 'False' stop_on_time_tag: 'False' subdir_cadence_s: '3600' + top_level_dir: directory_name uuid: '' vlen: '1' states: bus_sink: false bus_source: false bus_structure: null - coordinate: [659, 110] + coordinate: [656, 108.0] rotation: 0 - state: true + state: enabled - name: samp_rate id: parameter parameters: @@ -105,8 +139,10 @@ blocks: address: tcp://127.0.0.1:5558 affinity: '' alias: '' + bind: 'False' comment: '' hwm: '-1' + key: '' maxoutbuf: '0' minoutbuf: '0' pass_tags: 'True' @@ -122,7 +158,8 @@ blocks: state: true connections: -- [zeromq_sub_source_0, '0', gr_digital_rf_digital_rf_channel_sink_0, '0'] +- [zeromq_sub_source_0, '0', gr_digital_rf_digital_rf_sink_0, '0'] metadata: file_format: 1 + grc_version: 3.10.9.2 diff --git a/radio/radio_save_spec/radio_save_spec.grc b/radio/radio_save_spec/radio_save_spec.grc index c9273274..20d05d46 100644 --- a/radio/radio_save_spec/radio_save_spec.grc +++ b/radio/radio_save_spec/radio_save_spec.grc @@ -1,6 +1,7 @@ options: parameters: author: '' + catch_exceptions: 'True' category: '[GRC Hier Blocks]' cmake_opt: '' comment: '' @@ -125,25 +126,26 @@ blocks: \ = metadata[\"num_bins\"]\n tsys = metadata[\"tsys\"]\n tcal = metadata[\"\ tcal\"]\n cal_pwr = metadata[\"cal_pwr\"]\n vslr = metadata[\"vslr\"]\n\ \ glat = metadata[\"glat\"]\n glon = metadata[\"glon\"]\n soutrack\ - \ = metadata[\"soutrack\"]\n bsw = metadata[\"bsw\"]\n return (\n \ - \ motor_az,\n motor_el,\n freq / pow(10, 6),\n samp_rate\ - \ / pow(10, 6),\n num_integrations,\n num_bins,\n tsys,\n\ - \ tcal,\n cal_pwr,\n vslr,\n glat,\n glon,\n\ - \ soutrack,\n bsw,\n )\n\n\nclass blk(\n gr.sync_block\n\ - ): # other base classes are basic_block, decim_block, interp_block\n \"\"\ - \"Embedded Python Block example - a simple multiply const\"\"\"\n\n def __init__(\n\ - \ self, directory=\".\", filename=\"test.rad\", vec_length=4096\n \ - \ ): # only default arguments here\n \"\"\"arguments to this function\ - \ show up as parameters in GRC\"\"\"\n gr.sync_block.__init__(\n \ - \ self,\n name=\"Embedded Python Block\", # will show up\ - \ in GRC\n in_sig=[(np.float32, vec_length)],\n out_sig=None,\n\ - \ )\n # if an attribute with the same name as a parameter is found,\n\ - \ # a callback is registered (properties work, too).\n self.directory\ - \ = directory\n self.filename = filename\n self.vec_length = vec_length\n\ - \ self.obsn = 0\n\n def work(self, input_items, output_items):\n \ - \ \"\"\"example: multiply with constant\"\"\"\n file = open(pathlib.Path(self.directory,\ - \ self.filename), \"a+\")\n tags = self.get_tags_in_window(0, 0, len(input_items[0]))\n\ - \ latest_data_dict = {\n pmt.to_python(tag.key): pmt.to_python(tag.value)\ + \ = metadata[\"soutrack\"]\n bsw = metadata[\"bsw\"]\n cal_on = metadata[\"\ + cal_on\"]\n return (\n motor_az,\n motor_el,\n freq\ + \ / pow(10, 6),\n samp_rate / pow(10, 6),\n num_integrations,\n\ + \ num_bins,\n tsys,\n tcal,\n cal_pwr,\n \ + \ vslr,\n glat,\n glon,\n soutrack,\n bsw,\n \ + \ )\n\n\nclass blk(\n gr.sync_block\n): # other base classes are basic_block,\ + \ decim_block, interp_block\n \"\"\"Embedded Python Block example - a simple\ + \ multiply const\"\"\"\n\n def __init__(\n self, directory=\".\",\ + \ filename=\"test.rad\", vec_length=4096\n ): # only default arguments here\n\ + \ \"\"\"arguments to this function show up as parameters in GRC\"\"\"\ + \n gr.sync_block.__init__(\n self,\n name=\"Embedded\ + \ Python Block\", # will show up in GRC\n in_sig=[(np.float32, vec_length)],\n\ + \ out_sig=None,\n )\n # if an attribute with the same\ + \ name as a parameter is found,\n # a callback is registered (properties\ + \ work, too).\n self.directory = directory\n self.filename = filename\n\ + \ self.vec_length = vec_length\n self.obsn = 0\n\n def work(self,\ + \ input_items, output_items):\n \"\"\"example: multiply with constant\"\ + \"\"\n file = open(pathlib.Path(self.directory, self.filename), \"a+\"\ + )\n tags = self.get_tags_in_window(0, 0, len(input_items[0]))\n \ + \ latest_data_dict = {\n pmt.to_python(tag.key): pmt.to_python(tag.value)\ \ for tag in tags\n }\n yr, da, hr, mn, sc = parse_time(latest_data_dict[\"\ rx_time\"])\n (\n aznow,\n elnow,\n \ \ freq,\n bw,\n integ,\n nfreq,\n \ @@ -199,8 +201,10 @@ blocks: address: tcp://127.0.0.1:5562 affinity: '' alias: '' + bind: 'False' comment: '' hwm: '-1' + key: '' maxoutbuf: '0' minoutbuf: '0' pass_tags: 'True' @@ -220,3 +224,4 @@ connections: metadata: file_format: 1 + grc_version: 3.10.9.2 diff --git a/srt/daemon/daemon.py b/srt/daemon/daemon.py index d3d5dc65..6e899f60 100644 --- a/srt/daemon/daemon.py +++ b/srt/daemon/daemon.py @@ -11,6 +11,7 @@ from xmlrpc.client import ServerProxy from pathlib import Path from operator import add +import os import zmq import json @@ -26,7 +27,16 @@ ) from .utilities.object_tracker import EphemerisTracker from .utilities.functions import azel_within_range, get_spectrum +from .utilities.calibration_functions import basic_cold_sky_calibration_fit, additive_noise_calibration_fit +#pull astropy things into the daemon too for now so we can create skycoord objects here more easily + +from astropy.coordinates import SkyCoord, EarthLocation +from astropy.coordinates import ICRS, Galactic, FK4, CIRS, AltAz, LSR +from astropy.utils.iers.iers import conf +from astropy.table import Table +from astropy.time import Time +import astropy.units as u class SmallRadioTelescopeDaemon: """ @@ -74,20 +84,29 @@ def __init__(self, config_directory, config_dict): (point["azimuth"], point["elevation"]) for point in config_dict["HORIZON_POINTS"] ] + self.motor_type = config_dict["MOTOR_TYPE"] self.motor_port = config_dict["MOTOR_PORT"] self.motor_baudrate = config_dict["MOTOR_BAUDRATE"] + self.radio_center_frequency = config_dict["RADIO_CF"] self.radio_sample_frequency = config_dict["RADIO_SF"] + self.radio_rf_gain = config_dict["RADIO_RF_GAIN"] self.radio_frequency_correction = config_dict["RADIO_FREQ_CORR"] self.radio_num_bins = config_dict["RADIO_NUM_BINS"] self.radio_integ_cycles = config_dict["RADIO_INTEG_CYCLES"] self.radio_autostart = config_dict["RADIO_AUTOSTART"] self.num_beamswitches = config_dict["NUM_BEAMSWITCHES"] self.beamwidth = config_dict["BEAMWIDTH"] + self.cal_type = config_dict["CAL_TYPE"] + self.cal_cycles = config_dict["CAL_INTEGRATION_CYCLES"] self.temp_sys = config_dict["TSYS"] self.temp_cal = config_dict["TCAL"] self.save_dir = config_dict["SAVE_DIRECTORY"] + self.dashboard_refresh_rate = config_dict["DASHBOARD_REFRESH_MS"]/1000.0 + + self.npoints = 5 #default size of grid for npoint scan + self.radio_calibrator_state = False # Generate Default Calibration Values # Values are Set Up so that Uncalibrated and Calibrated Spectra are the Same Values @@ -114,7 +133,6 @@ def __init__(self, config_directory, config_dict): Path(config_directory, "sky_coords.csv").absolute()), ) self.ephemeris_locations = self.ephemeris_tracker.get_all_azimuth_elevation() - self.ephemeris_vlsr = self.ephemeris_tracker.get_all_vlsr() self.ephemeris_time_locs = self.ephemeris_tracker.get_all_azel_time() self.current_vlsr = 0.0 self.ephemeris_cmd_location = None @@ -127,8 +145,13 @@ def __init__(self, config_directory, config_dict): self.az_limits, self.el_limits, ) + + #vars pulled in from rotor definition + #self.rotor.rotor_loop_cadence + #self.rotor.pointing_accuracy + print("test", self.stow_location) - self.rotor_location = self.stow_location + self.rotor_location = (0,0)#self.stow_location self.rotor_destination = self.stow_location self.rotor_offsets = (0.0, 0.0) self.rotor_cmd_location = tuple( @@ -167,7 +190,7 @@ def log_message(self, message): self.command_error_logs.append((time(), message)) print(message) - def n_point_scan(self, object_id): + def n_point_scan(self, object_id, grid_size=5): """Runs an N-Point (25) Scan About an Object Parameters @@ -181,21 +204,25 @@ def n_point_scan(self, object_id): """ self.ephemeris_cmd_location = None self.radio_queue.put(("soutrack", object_id)) + + scan_center = self.ephemeris_locations[object_id] + self.ephemeris_cmd_location = object_id #tell tracking process to follow scan center + # Send vlsr to radio queue - cur_vlsr = self.ephemeris_vlsr[object_id] - self.radio_queue.put(("vlsr", float(cur_vlsr))) - self.current_vlsr = cur_vlsr - N_pnt_default = 25 + #cur_vlsr = self.ephemeris_vlsr[object_id] + #self.radio_queue.put(("vlsr", float(self.current_vlsr))) + #self.current_vlsr = cur_vlsr + N_pnt_default = grid_size**2 rotor_loc = [] pwr_list = [] # - scan_center = self.ephemeris_locations[object_id] - np_sides = [5, 5] + + np_sides = [grid_size, grid_size] for scan in range(N_pnt_default): - self.log_message( - "{0} of {1} point scan.".format(scan, N_pnt_default)) - i = (scan // 5) - 2 - j = (scan % 5) - 2 + scan_center = self.ephemeris_locations[object_id] #recompute target position for every iteration + self.log_message("{0} of {1} point scan.".format(scan, N_pnt_default)) + i = (scan // grid_size) - int(grid_size/2) + j = (scan % grid_size) - int(grid_size/2) el_dif = i * self.beamwidth * 0.5 az_dif_scalar = np.cos((scan_center[1] + el_dif) * np.pi / 180.0) # Avoid issues where you get close to the zenith @@ -207,7 +234,7 @@ def n_point_scan(self, object_id): new_rotor_offsets = (az_dif, el_dif) if self.rotor.angles_within_bounds(*scan_center): - self.rotor_destination = scan_center + #self.rotor_destination = self.ephemeris_locations[object_id] #this line is probably redundant self.point_at_offset(*new_rotor_offsets) rotor_loc.append(self.rotor_location) sleep(5) @@ -238,14 +265,12 @@ def beam_switch(self, object_id): """ self.ephemeris_cmd_location = None self.radio_queue.put(("soutrack", object_id)) - # Send vlsr to radio queue - cur_vlsr = self.ephemeris_vlsr[object_id] - self.radio_queue.put(("vlsr", float(cur_vlsr))) - self.current_vlsr = cur_vlsr + new_rotor_destination = self.ephemeris_locations[object_id] rotor_loc = [] pwr_list = [] for j in range(0, 3 * self.num_beamswitches): + new_rotor_destination = self.ephemeris_locations[object_id] #recompute target position for every iteration self.radio_queue.put(("beam_switch", j + 1)) az_dif_scalar = np.cos(new_rotor_destination[1] * np.pi / 180.0) az_dif = (j % 3 - 1) * self.beamwidth / az_dif_scalar @@ -279,16 +304,13 @@ def point_at_object(self, object_id): """ self.rotor_offsets = (0.0, 0.0) self.radio_queue.put(("soutrack", object_id)) - # Send vlsr to radio queue - cur_vlsr = self.ephemeris_vlsr[object_id] - self.radio_queue.put(("vlsr", float(cur_vlsr))) - self.current_vlsr = cur_vlsr + new_rotor_cmd_location = self.ephemeris_locations[object_id] if self.rotor.angles_within_bounds(*new_rotor_cmd_location): self.ephemeris_cmd_location = object_id self.rotor_destination = new_rotor_cmd_location self.rotor_cmd_location = new_rotor_cmd_location - while not azel_within_range(self.rotor_location, self.rotor_cmd_location): + while not azel_within_range(self.rotor_location, self.rotor_cmd_location, bounds=(self.rotor.pointing_accuracy,self.rotor.pointing_accuracy)): sleep(0.1) else: self.log_message(f"Object {object_id} Not in Motor Bounds") @@ -308,28 +330,131 @@ def point_at_azel(self, az, el): ------- None """ - # cur_vlsr = self.ephemeris_tracker.calculate_vlsr_azel((az,el)) - # self.radio_queue.put(("vlsr",cur_vlsr)) - # self.current_vlsr = cur_vlsr - self.ephemeris_cmd_location = None + + #define a skycoord object with az el position info + + #obstime = Time.now() + + #sky_coord = SkyCoord(AltAz(obstime=obstime, location=self.ephemeris_tracker.location, alt=el * u.deg, az=az * u.deg)) + #self.ephemeris_tracker.target=sky_coord + + if self.ephemeris_cmd_location is not None: + self.ephemeris_cmd_location = None #clear tracking command + sleep(0.05) #give the ephemeris tracker time to finish and stop causing trouble + self.rotor_offsets = (0.0, 0.0) # Send az and el angles to sources track for the radio self.radio_queue.put(("soutrack", f"azel_{az}_{el}")) - # Send vlsr to radio queue - cur_vlsr = self.ephemeris_tracker.calculate_vlsr_azel((az, el)) - self.current_vlsr = cur_vlsr - self.radio_queue.put(("vlsr", float(cur_vlsr))) new_rotor_destination = (az, el) new_rotor_cmd_location = new_rotor_destination if self.rotor.angles_within_bounds(*new_rotor_cmd_location): self.rotor_destination = new_rotor_destination self.rotor_cmd_location = new_rotor_cmd_location - while not azel_within_range(self.rotor_location, self.rotor_cmd_location): + while not azel_within_range(self.rotor_location, self.rotor_cmd_location, bounds=(self.rotor.pointing_accuracy,self.rotor.pointing_accuracy)): + self.rotor_destination = new_rotor_destination + self.rotor_cmd_location = new_rotor_cmd_location + sleep(0.1) + else: + + self.log_message(f"Object at {new_rotor_cmd_location} Not in Motor Bounds") + + def point_at_galactic(self, l_pos, b_pos): + """Points Antenna at a Specific Galactic longitude and lattitude + + Parameters + ---------- + l_pos : float + longitude, in degrees, to turn antenna towards + b_pos : float + lattitude, in degrees, to point antenna upwards at + duration : float + duration in seconds to continue tracking coordinate + + Returns + ------- + None + """ + + #clear preexisting track (prevents target update collisions) + self.ephemeris_cmd_location = None + + #define a skycoord object with galactic coord info + + sky_coord = SkyCoord(l_pos, b_pos, frame='galactic', unit=u.deg, location=self.ephemeris_tracker.location) + self.ephemeris_tracker.target=sky_coord + object_id = "target" + sleep(0.05) + + + #if (self.motor_type == "W1XM_BIG_DISH"): + #rotor is smart enough to directly handle the command, need to eventually restructure so we can pass it nicely + + # self.log_message("direct galactic coordinate commands not yet supported for your rotor. using standard tracking") + #else: + #rotor needs command converted to az-el + # self.log_message("direct galactic coordinate commands not yet supported for your rotor. using standard tracking") + + self.rotor_offsets = (0.0, 0.0) + self.radio_queue.put(("soutrack", f"radec_{l_pos}_{b_pos}")) + + azel_frame = AltAz(obstime=Time.now(), location=self.ephemeris_tracker.location, alt=self.rotor_location[1] * u.deg, az=self.rotor_location[0] * u.deg) + target_azel = sky_coord.transform_to(azel_frame) + new_rotor_destination = (target_azel.az.degree, target_azel.alt.degree) + new_rotor_cmd_location = tuple(map(add, new_rotor_destination, self.rotor_offsets)) + + if self.rotor.angles_within_bounds(*new_rotor_cmd_location): + self.ephemeris_cmd_location = object_id + self.rotor_destination = new_rotor_cmd_location + self.rotor_cmd_location = new_rotor_cmd_location + while not azel_within_range(self.rotor_location, self.rotor_cmd_location, bounds=(self.rotor.pointing_accuracy,self.rotor.pointing_accuracy)): + sleep(0.1) + else: + self.log_message(f"Object {object_id} Not in Motor Bounds") + self.ephemeris_cmd_location = None + + def point_at_radec(self, ra_pos, dec_pos): + """Points Antenna at a specific ICRS coordinate in Ra Dec + (Bigdish uses J2000) + + Parameters + ---------- + ra_pos : float + right ascension, in degrees, to turn antenna towards + dec_pos : float + declination, in degrees, to point antenna upwards at + duration : float + duration in seconds to continue tracking coordinate + + Returns + ------- + None + """ + + #clear preexisting track (prevents target update collisions) + self.ephemeris_cmd_location = None + + sky_coord = SkyCoord(ra_pos, dec_pos, frame='icrs', unit=u.deg, location=self.ephemeris_tracker.location) + self.ephemeris_tracker.target=sky_coord + object_id = "target" + + self.rotor_offsets = (0.0, 0.0) + self.radio_queue.put(("soutrack", f"radec_{ra_pos}_{dec_pos}")) + + azel_frame = AltAz(obstime=Time.now(), location=self.ephemeris_tracker.location, alt=self.rotor_location[1] * u.deg, az=self.rotor_location[0] * u.deg) + target_azel = sky_coord.transform_to(azel_frame) + new_rotor_destination = (target_azel.az.degree, target_azel.alt.degree) + new_rotor_cmd_location = tuple(map(add, new_rotor_destination, self.rotor_offsets)) + + if self.rotor.angles_within_bounds(*new_rotor_cmd_location): + self.ephemeris_cmd_location = object_id + self.rotor_destination = new_rotor_cmd_location + self.rotor_cmd_location = new_rotor_cmd_location + while not azel_within_range(self.rotor_location, self.rotor_cmd_location, bounds=(self.rotor.pointing_accuracy,self.rotor.pointing_accuracy)): sleep(0.1) else: - self.log_message( - f"Object at {new_rotor_cmd_location} Not in Motor Bounds") + self.log_message(f"Object {object_id} Not in Motor Bounds") + self.ephemeris_cmd_location = None def point_at_offset(self, az_off, el_off): """From the Current Object or Position Pointed At, Move to an Offset of That Location @@ -352,25 +477,28 @@ def point_at_offset(self, az_off, el_off): if self.rotor.angles_within_bounds(*new_rotor_cmd_location): self.rotor_offsets = new_rotor_offsets self.rotor_cmd_location = new_rotor_cmd_location - while not azel_within_range(self.rotor_location, self.rotor_cmd_location): + while not azel_within_range(self.rotor_location, self.rotor_cmd_location, bounds=(self.rotor.pointing_accuracy,self.rotor.pointing_accuracy)): sleep(0.1) else: self.log_message(f"Offset {new_rotor_offsets} Out of Bounds") def stow(self): - """Moves the Antenna Back to Its Stow Location + """Moves the Antenna Back to its Stow Location Returns ------- None """ - self.ephemeris_cmd_location = None - self.radio_queue.put(("soutrack", "at_stow")) - self.rotor_offsets = (0.0, 0.0) - self.rotor_destination = self.stow_location - self.rotor_cmd_location = self.stow_location - while not azel_within_range(self.rotor_location, self.rotor_cmd_location): - sleep(0.1) + + self.point_at_azel(self.stow_location[0], self.stow_location[1]) + + #self.ephemeris_cmd_location = None + #self.radio_queue.put(("soutrack", "at_stow")) + #self.rotor_offsets = (0.0, 0.0) + #self.rotor_destination = self.stow_location + #self.rotor_cmd_location = self.stow_location + #while not azel_within_range(self.rotor_location, self.rotor_cmd_location): + # sleep(0.1) def calibrate(self): """Runs Calibration Processing and Pushes New Values to Processing Script @@ -379,15 +507,112 @@ def calibrate(self): ------- None """ - sleep( - self.radio_num_bins * self.radio_integ_cycles / self.radio_sample_frequency - ) - radio_cal_task = RadioCalibrateTask( - self.radio_num_bins, - self.config_directory, - ) - radio_cal_task.start() - radio_cal_task.join(30) + + #kill any running file save operations since we're about to scramble them + + if self.radio_save_task is not None: + self.radio_save_task.terminate() + + # erase existing calibration + self.cal_values = [1.0 for _ in range(self.radio_num_bins)] + self.cal_power = 1.0 + + self.radio_queue.put(("cal_pwr", self.cal_power)) + self.radio_queue.put(("cal_values", self.cal_values)) + + ''' + simple cold sky cal for the basic SRT + ''' + + if self.cal_type == "COLD_SKY": + #define filenames for calibration measurements + cold_sky_name = "cold_sky.fits" + + #erase prior calibration files if present + cold_sky_file=str(Path(self.config_directory, cold_sky_name).absolute()) + if os.path.exists(cold_sky_file): + os.remove(cold_sky_file) + + self.log_message("Starting cold calibration reference measurement") + + #start saving new calibration file + sleep(0.1+2*self.radio_num_bins * self.radio_integ_cycles / self.radio_sample_frequency) + self.start_recording(name=cold_sky_name, file_dir=self.config_directory) + sleep((self.cal_cycles+1)*self.radio_num_bins* self.radio_integ_cycles/ self.radio_sample_frequency) + self.stop_recording() + + + ### compute calibration corrections + + cal_values, cal_power = basic_cold_sky_calibration_fit(cold_sky_file, self.temp_sys, self.temp_cal, 20) + + + + + ''' + if we have a noise diode to use for calibration we need to make multiple measurements + ''' + + if self.cal_type == "NOISE_DIODE": + #define filenames for calibration measurements + cold_sky_name = "cold_sky.fits" + cal_ref_name = "cold_sky_plus_cal.fits" + + #erase prior calibration files if present + cold_sky_file=str(Path(self.config_directory, cold_sky_name).absolute()) + cal_ref_file=str(Path(self.config_directory, cal_ref_name).absolute()) + + if os.path.exists(cold_sky_file): + os.remove(cold_sky_file) + + if os.path.exists(cal_ref_file): + os.remove(cal_ref_file) + + #enable calibrator and wait for the idiotically long settling time the filters currently have + #(need to fix that eventually so integration intervals are fully independent like they should be) + + self.log_message("Starting hot calibration reference measurement") + + self.set_calibrator_state(True) + sleep(0.1+2*self.radio_num_bins * self.radio_integ_cycles / self.radio_sample_frequency) + self.start_recording(name=cal_ref_name, file_dir=self.config_directory) + sleep((self.cal_cycles+1)*self.radio_num_bins* self.radio_integ_cycles/ self.radio_sample_frequency) + self.stop_recording() + + #disable calibrator and wait for the idiotically long settling time the filters currently have + #(need to fix that eventually so integration intervals are fully independent like they should be) + + self.log_message("Starting cold calibration reference measurement") + + self.set_calibrator_state(False) + sleep(0.1+2*self.radio_num_bins * self.radio_integ_cycles / self.radio_sample_frequency) + self.start_recording(name=cold_sky_name, file_dir=self.config_directory) + sleep((self.cal_cycles+1)*self.radio_num_bins* self.radio_integ_cycles/ self.radio_sample_frequency) + self.stop_recording() + + cal_values, cal_power = additive_noise_calibration_fit(cold_sky_file, cal_ref_file, self.temp_sys, self.temp_cal, 20) + + + #erase old cal file to prevent wierdness + + calibration_path = Path(self.config_directory, "calibration.json") + if os.path.exists(calibration_path): + os.remove(calibration_path) + + #save result + + file_output = { + "cal_pwr": cal_power, + "cal_values": cal_values.tolist(), + } + with open(calibration_path, "w") as outfile: + json.dump(file_output, outfile) + + #write corrections back to processing + + #readback from file is to circumvent a wierd formatting issue I can't figure out + + sleep(0.1) path = Path(self.config_directory, "calibration.json") with open(path, "r") as input_file: cal_data = json.load(input_file) @@ -395,9 +620,11 @@ def calibrate(self): self.cal_power = cal_data["cal_pwr"] self.radio_queue.put(("cal_pwr", self.cal_power)) self.radio_queue.put(("cal_values", self.cal_values)) + + self.log_message("Calibration Done") - def start_recording(self, name): + def start_recording(self, name, file_dir): """Starts Recording Data Parameters @@ -412,14 +639,14 @@ def start_recording(self, name): if self.radio_save_task is None: if name is None: self.radio_save_task = RadioSaveRawTask( - self.radio_sample_frequency, self.save_dir, name + self.radio_sample_frequency, file_dir, name ) elif name.endswith(".rad"): name = None if name == "*.rad" else name self.radio_save_task = RadioSaveSpecRadTask( self.radio_sample_frequency, self.radio_num_bins, - self.save_dir, + file_dir, name, ) elif name.endswith(".fits"): @@ -427,12 +654,12 @@ def start_recording(self, name): self.radio_save_task = RadioSaveSpecFitsTask( self.radio_sample_frequency, self.radio_num_bins, - self.save_dir, + file_dir, name, ) else: self.radio_save_task = RadioSaveRawTask( - self.radio_sample_frequency, self.save_dir, name + self.radio_sample_frequency, file_dir, name ) self.radio_save_task.start() else: @@ -448,6 +675,15 @@ def stop_recording(self): if self.radio_save_task is not None: self.radio_save_task.terminate() self.radio_save_task = None + + def set_npoints(self, n): + """Set the number of points for the N point scan + + Parameters + ---------- + npoints : number of points along grid edge for npoint scan + """ + self.npoints = n def set_freq(self, frequency): """Set the Frequency of the Processing Script @@ -514,6 +750,56 @@ def set_samp_rate(self, samp_rate): self.radio_sample_frequency = samp_rate self.radio_queue.put(("samp_rate", self.radio_sample_frequency)) + def set_rf_gain(self, rf_gain): + """Set the rf gain of the radio + + Note that this stops any currently running raw saving tasks + + Parameters + ---------- + rf_gain : float + rf_gain for the SDR in dB + + Returns + ------- + None + """ + if self.radio_save_task is not None: + self.radio_save_task.terminate() + #save old gain and cal values + #old_gain = self.radio_rf_gain + #old_cal_values = self.cal_values + + self.radio_rf_gain = rf_gain + #self.cal_values = old_cal_values* 10**(0.1*(old_gain - self.radio_rf_gain)) + + self.radio_queue.put(("rf_gain", self.radio_rf_gain)) + #self.radio_queue.put(("cal_values", self.cal_values)) + + def set_calibrator_state(self, calibrator_state): + """Set the state of the calibrator via radio GPIO + + Note that this is highly system specific and must be programmed appropriately + + Parameters + ---------- + calibrator_state : boolean + whether the calibrator is on + + Returns + ------- + None + """ + if self.cal_type == "NOISE_DIODE": + #customize for appropriate control scheme + self.radio_calibrator_state = calibrator_state + self.radio_queue.put(("cal_on", self.radio_calibrator_state)) + #sleep(0.1) + + else: + self.log_message("Noise Source Not Implemented") + + def quit(self): """Stops the Daemon Process @@ -544,7 +830,7 @@ def find_object_location(self, name): self.ephemeris_cmd_location = name self.rotor_destination = new_rotor_cmd_location self.rotor_cmd_location = new_rotor_cmd_location - while not azel_within_range(self.rotor_location, self.rotor_cmd_location): + while not azel_within_range(self.rotor_location, self.rotor_cmd_location, bounds=(self.rotor.pointing_accuracy,self.rotor.pointing_accuracy)): sleep(0.1) else: self.log_message(f"Object {name} Not in Motor Bounds") @@ -564,32 +850,55 @@ def update_ephemeris_location(self): if last_updated_time is None or time() - last_updated_time > 10: last_updated_time = time() self.ephemeris_tracker.update_all_az_el() - self.ephemeris_locations = ( - self.ephemeris_tracker.get_all_azimuth_elevation() - ) - self.ephemeris_vlsr = self.ephemeris_tracker.get_all_vlsr() - self.ephemeris_time_locs = ( - self.ephemeris_tracker.get_all_azel_time() - ) + self.ephemeris_locations = (self.ephemeris_tracker.get_all_azimuth_elevation()) + + #only update vlsr when we care and only at cadence we update celestial coordinates + #do this in rotor update loop, not here + #self.ephemeris_vlsr = self.ephemeris_tracker.get_all_vlsr() + + self.ephemeris_time_locs = (self.ephemeris_tracker.get_all_azel_time()) + + #sleep(0.1) + + def update_target_position(self): + """Update position of point we are tracking separately from the display ephemeris + + Is Operated as an Infinite Looping Thread Function + + Returns + ------- + None + """ + last_updated_time = None + #last_ephemeris_cmd_location = None + tracking_update_time = 5 + + + while True: if self.ephemeris_cmd_location is not None: - new_rotor_destination = self.ephemeris_locations[ - self.ephemeris_cmd_location - ] - self.current_vlsr = self.ephemeris_vlsr[self.ephemeris_cmd_location] - new_rotor_cmd_location = tuple( - map(add, new_rotor_destination, self.rotor_offsets) - ) - if self.rotor.angles_within_bounds( - *new_rotor_destination - ) and self.rotor.angles_within_bounds(*new_rotor_cmd_location): - self.rotor_destination = new_rotor_destination - self.rotor_cmd_location = new_rotor_cmd_location - else: - self.log_message( - f"Object {self.ephemeris_cmd_location} moved out of motor bounds" - ) - self.ephemeris_cmd_location = None - sleep(1) + + if ((last_updated_time is None) or (time() - last_updated_time > tracking_update_time)): + #last_ephemeris_cmd_location = self.ephemeris_cmd_location + last_updated_time = time() + + self.ephemeris_tracker.update_track(self.ephemeris_cmd_location) + new_rotor_destination = self.ephemeris_tracker.get_track_azimuth_elevation(self.ephemeris_cmd_location) + new_rotor_cmd_location = tuple(map(add, new_rotor_destination, self.rotor_offsets)) + + if self.rotor.angles_within_bounds( + *new_rotor_destination) and self.rotor.angles_within_bounds(*new_rotor_cmd_location): + self.rotor_destination = new_rotor_destination + self.rotor_cmd_location = new_rotor_cmd_location + + else: + self.log_message(f"Object {self.ephemeris_cmd_location} moved out of motor bounds") + self.ephemeris_cmd_location = None + + sleep(0.1) + + + + def update_rotor_status(self): """Periodically Sets Rotor Azimuth and Elevation and Fetches New Antenna Position @@ -599,56 +908,80 @@ def update_rotor_status(self): Returns ------- None + """ + + last_time = time() + while True: + try: current_rotor_cmd_location = self.rotor_cmd_location if not azel_within_range( - self.rotor_location, current_rotor_cmd_location - ): + self.rotor_location, current_rotor_cmd_location, bounds=(self.rotor.pointing_accuracy,self.rotor.pointing_accuracy)): + self.rotor.set_azimuth_elevation( *current_rotor_cmd_location) - sleep(1) + + sleep(self.rotor.rotor_loop_cadence) start_time = time() while ( not azel_within_range( - self.rotor_location, current_rotor_cmd_location + self.rotor_location, current_rotor_cmd_location, bounds=(self.rotor.pointing_accuracy,self.rotor.pointing_accuracy) ) - ) and (time() - start_time) < 10: + ) and (time() - start_time) < 15: past_rotor_location = self.rotor_location self.rotor_location = self.rotor.get_azimuth_elevation() - print(past_rotor_location, self.rotor_location) + #print(past_rotor_location, self.rotor_location) if not self.rotor_location == past_rotor_location: - g_lat, g_lon = self.ephemeris_tracker.convert_to_gal_coord( - self.rotor_location - ) - self.radio_queue.put( - ("motor_az", float(self.rotor_location[0])) - ) - self.radio_queue.put( - ("motor_el", float(self.rotor_location[1])) - ) + + obstime = Time.now() + + g_lat, g_lon = self.ephemeris_tracker.convert_to_gal_coord(self.rotor_location) + self.radio_queue.put(("motor_az", float(self.rotor_location[0]))) + self.radio_queue.put(("motor_el", float(self.rotor_location[1]))) self.radio_queue.put(("glat", g_lat)) self.radio_queue.put(("glon", g_lon)) - sleep(0.5) + + #compute vlsr for where we're actually pointed. we don't really care if it's theoretically different for the target object + + azel_frame = AltAz(obstime=obstime, location=self.ephemeris_tracker.location, alt=self.rotor_location[1] * u.deg, az=self.rotor_location[0] * u.deg) + sky_coord = SkyCoord(azel_frame) + self.current_vlsr = self.ephemeris_tracker.calculate_vlsr(sky_coord,obstime) + self.radio_queue.put(("vlsr", float(self.current_vlsr))) + + sleep(self.rotor.rotor_loop_cadence) else: - past_rotor_location = self.rotor_location - self.rotor_location = self.rotor.get_azimuth_elevation() - print(self.rotor_location) - if not self.rotor_location == past_rotor_location: - g_lat, g_lon = self.ephemeris_tracker.convert_to_gal_coord( - self.rotor_location - ) - self.radio_queue.put( - ("motor_az", float(self.rotor_location[0])) - ) - self.radio_queue.put( - ("motor_el", float(self.rotor_location[1])) - ) + + if (time() - last_time) > 5 : #don't bother recomputing the celestial coordinates so often if we're not actally moving + + self.rotor.set_azimuth_elevation(*current_rotor_cmd_location) #always reissue pointing commands periodically and let rotor decide whether to adjust + #sleep(0.3) #move this to the pointing routine in motors.py where it belongs + + past_rotor_location = self.rotor_location + self.rotor_location = self.rotor.get_azimuth_elevation() + + obstime = Time.now() + + g_lat, g_lon = self.ephemeris_tracker.convert_to_gal_coord(self.rotor_location) + self.radio_queue.put(("motor_az", float(self.rotor_location[0]))) + self.radio_queue.put(("motor_el", float(self.rotor_location[1]))) self.radio_queue.put(("glat", g_lat)) self.radio_queue.put(("glon", g_lon)) - sleep(1) + #compute vlsr for where we're actually pointed. we don't really care if it's theoretically different for the target object + + azel_frame = AltAz(obstime=obstime, location=self.ephemeris_tracker.location, alt=self.rotor_location[1] * u.deg, az=self.rotor_location[0] * u.deg) + sky_coord = SkyCoord(azel_frame) + self.current_vlsr = self.ephemeris_tracker.calculate_vlsr(sky_coord,obstime) + self.radio_queue.put(("vlsr", float(self.current_vlsr))) + + last_time = time() + + sleep(self.rotor.rotor_loop_cadence) #make it much more responsive to commands + #aparrently making this loop too fast causes stability issues. prolly need to tweak rotor level code a bit to not + #crash and burn ir a read comes in before it has a status update request comes before it has new data + except AssertionError as e: self.log_message(str(e)) except ValueError as e: @@ -682,6 +1015,7 @@ def update_status(self): "cal_loc": self.cal_location, "horizon_points": self.horizon_points, "center_frequency": self.radio_center_frequency, + "rf_gain": self.radio_rf_gain, "frequency_correction": self.radio_frequency_correction, "bandwidth": self.radio_sample_frequency, "motor_offsets": self.rotor_offsets, @@ -695,9 +1029,10 @@ def update_status(self): "n_point_data": self.n_point_data, "beam_switch_data": self.beam_switch_data, "time": time(), + "cal_state": self.radio_calibrator_state, } status_socket.send_json(status) - sleep(0.5) + sleep(self.dashboard_refresh_rate) def update_radio_settings(self): """Coordinates Sending XMLRPC Commands to the GNU Radio Script @@ -713,7 +1048,7 @@ def update_radio_settings(self): method, value = self.radio_queue.get() call = getattr(rpc_server, f"set_{method}") call(value) - sleep(0.1) + sleep(0.01) def update_command_queue(self): """Waits for New Commands Coming in Over ZMQ PUSH/PULL @@ -742,8 +1077,9 @@ def srt_daemon_main(self): # Create Infinite Looping Threads ephemeris_tracker_thread = Thread( - target=self.update_ephemeris_location, daemon=True - ) + target=self.update_ephemeris_location, daemon=True) + target_tracking_thread = Thread( + target=self.update_target_position, daemon=True) rotor_pointing_thread = Thread( target=self.update_rotor_status, daemon=True) command_queueing_thread = Thread( @@ -757,7 +1093,7 @@ def srt_daemon_main(self): self.radio_process_task.start() except RuntimeError as e: self.log_message(str(e)) - sleep(5) + sleep(10) #wait a bit for the radio to actually start up # Send Settings to the GNU Radio Script radio_params = { @@ -766,6 +1102,7 @@ def srt_daemon_main(self): self.radio_center_frequency + self.radio_frequency_correction, ), "Sample Rate": ("samp_rate", self.radio_sample_frequency), + "RF Gain": ("rf_gain", self.radio_rf_gain), "Motor Azimuth": ("motor_az", self.rotor_location[0]), "Motor Elevation": ("motor_el", self.rotor_location[1]), "Motor GalLat": ( @@ -791,6 +1128,7 @@ def srt_daemon_main(self): # Start Infinite Looping Update Threads ephemeris_tracker_thread.start() + target_tracking_thread.start() rotor_pointing_thread.start() command_queueing_thread.start() status_thread.start() @@ -814,7 +1152,7 @@ def srt_daemon_main(self): # If Command Starts With a Valid Object Name if command_parts[0] in self.ephemeris_locations: if command_parts[-1] == "n": # N-Point Scan About Object - self.n_point_scan(object_id=command_parts[0]) + self.n_point_scan(object_id=command_parts[0], grid_size=self.npoints) elif command_parts[-1] == "b": # Beam-Switch Away From Object self.beam_switch(object_id=command_parts[0]) else: # Point Directly At Object @@ -823,14 +1161,19 @@ def srt_daemon_main(self): self.stow() elif command_name == "cal": self.point_at_azel(*self.cal_location) + elif command_name == "calon": + self.set_calibrator_state(True) + elif command_name == "caloff": + self.set_calibrator_state(False) elif command_name == "calibrate": self.calibrate() + elif command_name == "npointset": + self.set_npoints(n=int(command_parts[1])) elif command_name == "quit": self.quit() elif command_name == "record": self.start_recording( - name=(None if len(command_parts) - <= 1 else command_parts[1]) + name=(None if len(command_parts) <= 1 else command_parts[1]), file_dir=self.save_dir ) elif command_name == "roff": self.stop_recording() @@ -838,8 +1181,9 @@ def srt_daemon_main(self): self.set_freq(frequency=float( command_parts[1]) * pow(10, 6)) elif command_name == "samp": - self.set_samp_rate(samp_rate=float( - command_parts[1]) * pow(10, 6)) + self.set_samp_rate(samp_rate=float(command_parts[1]) * pow(10, 6)) + elif command_name == "rf_gain": + self.set_rf_gain(rf_gain=float(command_parts[1])) elif command_name == "coords": self.set_coords( float(command_parts[1]), float(command_parts[2])) @@ -854,6 +1198,16 @@ def srt_daemon_main(self): float(command_parts[1]), float(command_parts[2]), ) + elif command_name == "galactic": + self.point_at_galactic( + float(command_parts[1]), + float(command_parts[2]), + ) + elif command_name == "radec": + self.point_at_radec( + float(command_parts[1]), + float(command_parts[2]), + ) elif command_name == "offset": self.point_at_offset( float(command_parts[1]), float(command_parts[2]) @@ -865,22 +1219,30 @@ def srt_daemon_main(self): elif command_name == "wait": sleep(float(command_parts[1])) # Wait Until Next Time H:M:S - elif command_name.split(":")[0] == "lst": - time_string = command_name.replace("LST:", "") + # command format is "wait_utc_hms hour:minute:second" with utc time + elif command_name == "wait_utc_hms": + time_string = command_parts[1] time_val = datetime.strptime(time_string, "%H:%M:%S") while time_val < datetime.utcfromtimestamp(time()): time_val += timedelta(days=1) time_delta = ( time_val - datetime.utcfromtimestamp(time()) ).total_seconds() + self.log_message(f'sleeping for {time_delta} seconds') sleep(time_delta) - elif len(command_name.split(":")) == 5: # Wait Until Y:D:H:M:S - time_val = datetime.strptime( - command_name, "%Y:%j:%H:%M:%S") + # Wait until next iso time + # command format is "wait_utc_iso %Y-%m-%dT%H:%M:%S.%f%z" with utc time + elif command_name == "wait_utc_iso": + time_val = datetime.fromisoformat( + command_parts[1]) time_delta = ( time_val - datetime.utcfromtimestamp(time()) ).total_seconds() - sleep(time_delta) + if time_delta > 0: + self.log_message(f'sleeping for {time_delta} seconds') + sleep(time_delta) + else: + self.log_message('target command time past. skipping wait') else: self.log_message(f"Command Not Identified '{command}'") self.command_queue.task_done() diff --git a/srt/daemon/radio_control/radio_process/calibrator_control_strobe.py b/srt/daemon/radio_control/radio_process/calibrator_control_strobe.py new file mode 100644 index 00000000..cf2ba4a5 --- /dev/null +++ b/srt/daemon/radio_control/radio_process/calibrator_control_strobe.py @@ -0,0 +1,64 @@ +""" +block to generate calibrator control commands for the X300 radio. +note that this block assumes a latency lower than the calibrator cycle time +and will break if that condition is not met + +Embedded Python Blocks: + +Each time this file is saved, GRC will instantiate the first class it finds +to get ports and parameters of your block. The arguments to __init__ will +be the parameters. All of them are required to have default values! +""" +import numpy as np +from gnuradio import gr +import pmt +import time +import uhd + +class msg_blk(gr.sync_block): + def __init__(self, calibrator_mask=0xFFF,cal_state=False): + gr.sync_block.__init__( + self, + name="calibrator_msg_block", + in_sig=None, + out_sig=None + ) + + #input parameters + self.calibrator_mask = calibrator_mask + self.cal_state = cal_state + + #derived variables + + self.last_cal_state = False + #self.cal_value = 0x000 + + self.message_port_register_in(pmt.intern('strobe')) + self.message_port_register_out(pmt.intern('command')) + + self.set_msg_handler(pmt.intern('strobe'), self.handle_msg) + + def handle_msg(self, msg): + + #send message to define next calibrator state change + + if self.last_cal_state != self.cal_state: + if self.cal_state: + cal_value = 0xFFF + else: + cal_value = 0x000 + + #issue command to toggle gpio + set_gpio = pmt.make_dict() + set_gpio = pmt.dict_add(set_gpio, pmt.to_pmt('bank'), pmt.to_pmt('FP0A')) + set_gpio = pmt.dict_add(set_gpio, pmt.to_pmt('attr'), pmt.to_pmt('OUT')) + set_gpio = pmt.dict_add(set_gpio, pmt.to_pmt('value'), pmt.from_double(cal_value)) + set_gpio = pmt.dict_add(set_gpio, pmt.to_pmt('mask'), pmt.from_double(self.calibrator_mask)) + + msg = pmt.make_dict() + msg = pmt.dict_add(msg, pmt.to_pmt('gpio'), set_gpio) + self.message_port_pub(pmt.intern('command'), msg) #issue message + + self.last_cal_state = self.cal_state + + #otherwise do nothing diff --git a/srt/daemon/radio_control/radio_process/radio_process.py b/srt/daemon/radio_control/radio_process/radio_process.py old mode 100644 new mode 100755 index 33b5f0e1..22f9c384 --- a/srt/daemon/radio_control/radio_process/radio_process.py +++ b/srt/daemon/radio_control/radio_process/radio_process.py @@ -6,7 +6,7 @@ # # GNU Radio Python Flow Graph # Title: radio_process -# GNU Radio version: 3.8.1.0 +# GNU Radio version: 3.10.9.2 from gnuradio import blocks import pmt @@ -20,24 +20,23 @@ from argparse import ArgumentParser from gnuradio.eng_arg import eng_float, intx from gnuradio import eng_notation +from gnuradio import uhd +import time from gnuradio import zeromq - -try: - import SimpleXMLRPCServer -except ModuleNotFoundError: - import xmlrpc.server as SimpleXMLRPCServer - +from xmlrpc.server import SimpleXMLRPCServer import threading -from . import add_clock_tags import math import numpy as np -import osmosdr -import time +#import radio_process_add_clock_tags as add_clock_tags # embedded python block +from . import add_clock_tags +#import radio_process_calibrator_control_strobe as calibrator_control_strobe # embedded python block +from . import calibrator_control_strobe class radio_process(gr.top_block): + def __init__(self, num_bins=256, num_integrations=100000): - gr.top_block.__init__(self, "radio_process") + gr.top_block.__init__(self, "radio_process", catch_exceptions=True) ################################################## # Parameters @@ -48,200 +47,137 @@ def __init__(self, num_bins=256, num_integrations=100000): ################################################## # Variables ################################################## - self.sinc_sample_locations = sinc_sample_locations = np.arange( - -np.pi * 4 / 2.0, np.pi * 4 / 2.0, np.pi / num_bins - ) - self.sinc_samples = sinc_samples = np.sinc( - sinc_sample_locations / np.pi) + + self.sinc_sample_locations = sinc_sample_locations = np.arange(-np.pi*4/2.0, np.pi*4/2.0, np.pi/num_bins) + self.sinc_samples = sinc_samples = np.sinc(sinc_sample_locations/np.pi) + self.freq = freq = 1420000000 self.vlsr = vlsr = np.nan self.tsys = tsys = 171 self.tcal = tcal = 290 - self.tag_period = tag_period = num_bins * num_integrations + self.tag_period = tag_period = num_bins*num_integrations self.soutrack = soutrack = "at_stow" - self.samp_rate = samp_rate = 2400000 + self.samp_rate = samp_rate = 2000000 + self.rf_gain = rf_gain = 10 + self.rf_freq = rf_freq = freq self.motor_el = motor_el = np.nan self.motor_az = motor_az = np.nan self.is_running = is_running = False self.glon = glon = np.nan self.glat = glat = np.nan - self.freq = freq = 1420000000 self.fft_window = fft_window = window.blackmanharris(num_bins) - self.custom_window = custom_window = sinc_samples * \ - np.hamming(4 * num_bins) + self.custom_window = custom_window = sinc_samples*np.hamming(4*num_bins) + self.calibrator_mask = calibrator_mask = 0b000000000011 self.cal_values = cal_values = np.repeat(np.nan, num_bins) self.cal_pwr = cal_pwr = 1 + self.cal_on = cal_on = False self.beam_switch = beam_switch = 0 ################################################## # Blocks ################################################## - self.zeromq_pub_sink_2_0 = zeromq.pub_sink( - gr.sizeof_float, num_bins, "tcp://127.0.0.1:5561", 100, False, -1 - ) - self.zeromq_pub_sink_2 = zeromq.pub_sink( - gr.sizeof_float, num_bins, "tcp://127.0.0.1:5560", 100, True, -1 - ) - self.zeromq_pub_sink_1_0 = zeromq.pub_sink( - gr.sizeof_float, num_bins, "tcp://127.0.0.1:5562", 100, True, -1 - ) - self.zeromq_pub_sink_1 = zeromq.pub_sink( - gr.sizeof_float, num_bins, "tcp://127.0.0.1:5563", 100, False, -1 - ) - self.zeromq_pub_sink_0_0 = zeromq.pub_sink( - gr.sizeof_gr_complex, 1, "tcp://127.0.0.1:5559", 100, False, -1 - ) - self.zeromq_pub_sink_0 = zeromq.pub_sink( - gr.sizeof_gr_complex, 1, "tcp://127.0.0.1:5558", 100, True, -1 - ) - self.xmlrpc_server_0 = SimpleXMLRPCServer.SimpleXMLRPCServer( - ("localhost", 5557), allow_none=True - ) + + self.zeromq_pub_sink_2_0 = zeromq.pub_sink(gr.sizeof_float, num_bins, 'tcp://127.0.0.1:5561', 100, False, (-1), '', True) + self.zeromq_pub_sink_2 = zeromq.pub_sink(gr.sizeof_float, num_bins, 'tcp://127.0.0.1:5560', 100, True, (-1), '', True) + self.zeromq_pub_sink_1_0 = zeromq.pub_sink(gr.sizeof_float, num_bins, 'tcp://127.0.0.1:5562', 100, True, (-1), '', True) + self.zeromq_pub_sink_1 = zeromq.pub_sink(gr.sizeof_float, num_bins, 'tcp://127.0.0.1:5563', 100, False, (-1), '', True) + self.zeromq_pub_sink_0_0 = zeromq.pub_sink(gr.sizeof_gr_complex, 1, 'tcp://127.0.0.1:5559', 100, False, (-1), '', True) + self.zeromq_pub_sink_0 = zeromq.pub_sink(gr.sizeof_gr_complex, 1, 'tcp://127.0.0.1:5558', 100, True, (-1), '', True) + self.xmlrpc_server_0 = SimpleXMLRPCServer(('localhost', 5557), allow_none=True) self.xmlrpc_server_0.register_instance(self) - self.xmlrpc_server_0_thread = threading.Thread( - target=self.xmlrpc_server_0.serve_forever - ) + self.xmlrpc_server_0_thread = threading.Thread(target=self.xmlrpc_server_0.serve_forever) self.xmlrpc_server_0_thread.daemon = True self.xmlrpc_server_0_thread.start() - self.osmosdr_source_0 = osmosdr.source( - args="numchan=" + str(1) + " " + "soapy=0" - ) - self.osmosdr_source_0.set_time_unknown_pps(osmosdr.time_spec_t()) - self.osmosdr_source_0.set_sample_rate(samp_rate) - self.osmosdr_source_0.set_center_freq(freq, 0) - self.osmosdr_source_0.set_freq_corr(0, 0) - self.osmosdr_source_0.set_gain(49, 0) - self.osmosdr_source_0.set_if_gain(0, 0) - self.osmosdr_source_0.set_bb_gain(0, 0) - self.osmosdr_source_0.set_antenna("", 0) - self.osmosdr_source_0.set_bandwidth(0, 0) - self.fft_vxx_0 = fft.fft_vcc(num_bins, True, fft_window, True, 3) - self.dc_blocker_xx_0 = filter.dc_blocker_cc( - num_bins * num_integrations, False) - self.blocks_tags_strobe_0_0 = blocks.tags_strobe( - gr.sizeof_gr_complex * 1, - pmt.to_pmt( - { - "num_bins": num_bins, - "samp_rate": samp_rate, - "num_integrations": num_integrations, - "motor_az": motor_az, - "motor_el": motor_el, - "freq": freq, - "tsys": tsys, - "tcal": tcal, - "cal_pwr": cal_pwr, - "vlsr": vlsr, - "glat": glat, - "glon": glon, - "soutrack": soutrack, - "bsw": beam_switch, - } + + #blocks_tags_strobe blocks need to come before slow radio startup commands + self.blocks_tags_strobe_0_0 = blocks.tags_strobe(gr.sizeof_gr_complex*1, pmt.to_pmt({"num_bins": num_bins, "samp_rate": samp_rate, "num_integrations": num_integrations, "motor_az": motor_az, "motor_el": motor_el, "freq": freq, "tsys": tsys, "tcal": tcal, "cal_pwr": cal_pwr, "vlsr": vlsr, "glat": glat, "glon": glon, "soutrack": soutrack, "bsw": beam_switch, "cal_on": cal_on}), tag_period, pmt.intern("metadata")) + self.blocks_tags_strobe_0 = blocks.tags_strobe(gr.sizeof_gr_complex*1, pmt.to_pmt(float(freq)), tag_period, pmt.intern("rx_freq")) + + + self.uhd_usrp_source_1 = uhd.usrp_source( + ",".join(("addr=172.25.14.11", '')), + uhd.stream_args( + cpu_format="fc32", + args='', + channels=list(range(0,1)), ), - tag_period, - pmt.intern("metadata"), - ) - self.blocks_tags_strobe_0 = blocks.tags_strobe( - gr.sizeof_gr_complex * 1, - pmt.to_pmt(float(freq)), - tag_period, - pmt.intern("rx_freq"), - ) - self.blocks_stream_to_vector_0_2 = blocks.stream_to_vector( - gr.sizeof_gr_complex * 1, num_bins - ) - self.blocks_stream_to_vector_0_1 = blocks.stream_to_vector( - gr.sizeof_gr_complex * 1, num_bins ) - self.blocks_stream_to_vector_0_0 = blocks.stream_to_vector( - gr.sizeof_gr_complex * 1, num_bins - ) - self.blocks_stream_to_vector_0 = blocks.stream_to_vector( - gr.sizeof_gr_complex * 1, num_bins - ) - self.blocks_skiphead_0 = blocks.skiphead( - gr.sizeof_gr_complex * 1, num_bins * num_integrations - ) - self.blocks_selector_0 = blocks.selector( - gr.sizeof_gr_complex * 1, 0, 0) + + self.uhd_usrp_source_1.set_samp_rate(samp_rate) + self.uhd_usrp_source_1.set_clock_source("external") + self.uhd_usrp_source_1.set_time_source("external") + _last_pps_time = self.uhd_usrp_source_1.get_time_last_pps().get_real_secs() + # Poll get_time_last_pps() every 50 ms until a change is seen + while(self.uhd_usrp_source_1.get_time_last_pps().get_real_secs() == _last_pps_time): + time.sleep(0.05) + # Set the time to PC time on next PPS + self.uhd_usrp_source_1.set_time_next_pps(uhd.time_spec(int(time.time()) + 1.0)) + # Sleep 1 second to ensure next PPS has come + time.sleep(1) + + #self.uhd_usrp_source_1.set_center_freq(rf_freq, 0) + self.uhd_usrp_source_1.set_center_freq(uhd.tune_request(self.rf_freq,self.samp_rate*0.6), 0) #offset tune + self.uhd_usrp_source_1.set_antenna("RX2", 0) + self.uhd_usrp_source_1.set_bandwidth(samp_rate, 0) + self.uhd_usrp_source_1.set_gain(rf_gain, 0) + self.uhd_usrp_source_1.set_auto_dc_offset(True, 0) + + + ##### Manually Configure USRP GPIO + self.uhd_usrp_source_1.set_gpio_attr('FP0A', 'CTRL', 0x000, 0xFFF ^ calibrator_mask) #set pins 2 and 3 manual + self.uhd_usrp_source_1.set_gpio_attr('FP0A', 'DDR', 0xFFF, calibrator_mask) #set pins 2 and 3 as output + self.uhd_usrp_source_1.set_gpio_attr('FP0A', 'OUT', 0x000 , calibrator_mask) + + + self.fft_vxx_0 = fft.fft_vcc(num_bins, True, fft_window, True, 3) + self.dc_blocker_xx_0 = filter.dc_blocker_cc((num_bins*num_integrations), False) + self.calibrator_control_strobe = calibrator_control_strobe.msg_blk(calibrator_mask=calibrator_mask, cal_state=cal_on) + self.blocks_stream_to_vector_0_2 = blocks.stream_to_vector(gr.sizeof_gr_complex*1, num_bins) + self.blocks_stream_to_vector_0_1 = blocks.stream_to_vector(gr.sizeof_gr_complex*1, num_bins) + self.blocks_stream_to_vector_0_0 = blocks.stream_to_vector(gr.sizeof_gr_complex*1, num_bins) + self.blocks_stream_to_vector_0 = blocks.stream_to_vector(gr.sizeof_gr_complex*1, num_bins) + self.blocks_skiphead_0 = blocks.skiphead(gr.sizeof_gr_complex*1, (num_bins*num_integrations)) + self.blocks_selector_0 = blocks.selector(gr.sizeof_gr_complex*1,0,0) self.blocks_selector_0.set_enabled(True) - self.blocks_multiply_const_xx_0 = blocks.multiply_const_ff( - 1.0 / float(num_integrations), num_bins - ) - self.blocks_multiply_const_vxx_1 = blocks.multiply_const_vff( - [(tsys + tcal) / (value * cal_pwr) for value in cal_values] - ) - self.blocks_multiply_const_vxx_0_0_0_0 = blocks.multiply_const_vcc( - custom_window[0:num_bins] - ) - self.blocks_multiply_const_vxx_0_0_0 = blocks.multiply_const_vcc( - custom_window[num_bins: 2 * num_bins] - ) - self.blocks_multiply_const_vxx_0_0 = blocks.multiply_const_vcc( - custom_window[2 * num_bins: 3 * num_bins] - ) - self.blocks_multiply_const_vxx_0 = blocks.multiply_const_vcc( - custom_window[-num_bins:] - ) - self.blocks_message_strobe_0 = blocks.message_strobe( - pmt.to_pmt(is_running), 100 - ) - self.blocks_integrate_xx_0 = blocks.integrate_ff( - num_integrations, num_bins) - self.blocks_delay_0_1 = blocks.delay( - gr.sizeof_gr_complex * 1, num_bins) - self.blocks_delay_0_0 = blocks.delay( - gr.sizeof_gr_complex * 1, num_bins * 2) - self.blocks_delay_0 = blocks.delay( - gr.sizeof_gr_complex * 1, num_bins * 3) - self.blocks_complex_to_mag_squared_0 = blocks.complex_to_mag_squared( - num_bins) + self.blocks_multiply_const_xx_0 = blocks.multiply_const_ff(1.0/float(num_integrations), num_bins) + self.blocks_multiply_const_vxx_1 = blocks.multiply_const_vff([(tsys + tcal)/(value * cal_pwr) for value in cal_values]) + self.blocks_multiply_const_vxx_0_0_0_0 = blocks.multiply_const_vcc(custom_window[0:num_bins]) + self.blocks_multiply_const_vxx_0_0_0 = blocks.multiply_const_vcc(custom_window[num_bins:2*num_bins]) + self.blocks_multiply_const_vxx_0_0 = blocks.multiply_const_vcc(custom_window[2*num_bins:3*num_bins]) + self.blocks_multiply_const_vxx_0 = blocks.multiply_const_vcc(custom_window[-num_bins:]) + self.blocks_message_strobe_0 = blocks.message_strobe(pmt.to_pmt(is_running), 100) + self.blocks_integrate_xx_0 = blocks.integrate_ff(num_integrations, num_bins) + self.blocks_delay_0_1 = blocks.delay(gr.sizeof_gr_complex*1, num_bins) + self.blocks_delay_0_0 = blocks.delay(gr.sizeof_gr_complex*1, (num_bins*2)) + self.blocks_delay_0 = blocks.delay(gr.sizeof_gr_complex*1, (num_bins*3)) + self.blocks_complex_to_mag_squared_0 = blocks.complex_to_mag_squared(num_bins) self.blocks_add_xx_0_0 = blocks.add_vcc(1) self.blocks_add_xx_0 = blocks.add_vcc(num_bins) self.add_clock_tags = add_clock_tags.clk(nsamps=tag_period) + ################################################## # Connections ################################################## + self.msg_connect((self.blocks_message_strobe_0, 'strobe'), (self.calibrator_control_strobe, 'strobe')) + self.msg_connect((self.calibrator_control_strobe, 'command'), (self.uhd_usrp_source_1, 'command')) self.connect((self.add_clock_tags, 0), (self.blocks_add_xx_0_0, 1)) self.connect((self.blocks_add_xx_0, 0), (self.fft_vxx_0, 0)) self.connect((self.blocks_add_xx_0_0, 0), (self.blocks_selector_0, 0)) - self.connect( - (self.blocks_complex_to_mag_squared_0, - 0), (self.blocks_integrate_xx_0, 0) - ) - self.connect((self.blocks_delay_0, 0), - (self.blocks_stream_to_vector_0_2, 0)) - self.connect((self.blocks_delay_0_0, 0), - (self.blocks_stream_to_vector_0_0, 0)) - self.connect((self.blocks_delay_0_1, 0), - (self.blocks_stream_to_vector_0_1, 0)) - self.connect( - (self.blocks_integrate_xx_0, 0), (self.blocks_multiply_const_xx_0, 0) - ) - self.connect((self.blocks_multiply_const_vxx_0, 0), - (self.blocks_add_xx_0, 0)) - self.connect((self.blocks_multiply_const_vxx_0_0, 0), - (self.blocks_add_xx_0, 1)) - self.connect( - (self.blocks_multiply_const_vxx_0_0_0, 0), (self.blocks_add_xx_0, 2) - ) - self.connect( - (self.blocks_multiply_const_vxx_0_0_0_0, 0), (self.blocks_add_xx_0, 3) - ) - self.connect((self.blocks_multiply_const_vxx_1, 0), - (self.zeromq_pub_sink_1, 0)) - self.connect( - (self.blocks_multiply_const_vxx_1, 0), (self.zeromq_pub_sink_1_0, 0) - ) - self.connect( - (self.blocks_multiply_const_xx_0, - 0), (self.blocks_multiply_const_vxx_1, 0) - ) - self.connect((self.blocks_multiply_const_xx_0, 0), - (self.zeromq_pub_sink_2, 0)) - self.connect( - (self.blocks_multiply_const_xx_0, 0), (self.zeromq_pub_sink_2_0, 0) - ) + self.connect((self.blocks_complex_to_mag_squared_0, 0), (self.blocks_integrate_xx_0, 0)) + self.connect((self.blocks_delay_0, 0), (self.blocks_stream_to_vector_0_2, 0)) + self.connect((self.blocks_delay_0_0, 0), (self.blocks_stream_to_vector_0_0, 0)) + self.connect((self.blocks_delay_0_1, 0), (self.blocks_stream_to_vector_0_1, 0)) + self.connect((self.blocks_integrate_xx_0, 0), (self.blocks_multiply_const_xx_0, 0)) + self.connect((self.blocks_multiply_const_vxx_0, 0), (self.blocks_add_xx_0, 0)) + self.connect((self.blocks_multiply_const_vxx_0_0, 0), (self.blocks_add_xx_0, 1)) + self.connect((self.blocks_multiply_const_vxx_0_0_0, 0), (self.blocks_add_xx_0, 2)) + self.connect((self.blocks_multiply_const_vxx_0_0_0_0, 0), (self.blocks_add_xx_0, 3)) + self.connect((self.blocks_multiply_const_vxx_1, 0), (self.zeromq_pub_sink_1, 0)) + self.connect((self.blocks_multiply_const_vxx_1, 0), (self.zeromq_pub_sink_1_0, 0)) + self.connect((self.blocks_multiply_const_xx_0, 0), (self.blocks_multiply_const_vxx_1, 0)) + self.connect((self.blocks_multiply_const_xx_0, 0), (self.zeromq_pub_sink_2, 0)) + self.connect((self.blocks_multiply_const_xx_0, 0), (self.zeromq_pub_sink_2_0, 0)) + self.connect((self.blocks_selector_0, 0), (self.dc_blocker_xx_0, 0)) self.connect((self.blocks_selector_0, 0), (self.zeromq_pub_sink_0, 0)) self.connect((self.blocks_selector_0, 0), @@ -249,32 +185,17 @@ def __init__(self, num_bins=256, num_integrations=100000): self.connect((self.blocks_skiphead_0, 0), (self.blocks_delay_0, 0)) self.connect((self.blocks_skiphead_0, 0), (self.blocks_delay_0_0, 0)) self.connect((self.blocks_skiphead_0, 0), (self.blocks_delay_0_1, 0)) - self.connect((self.blocks_skiphead_0, 0), - (self.blocks_stream_to_vector_0, 0)) - self.connect( - (self.blocks_stream_to_vector_0, - 0), (self.blocks_multiply_const_vxx_0, 0) - ) - self.connect( - (self.blocks_stream_to_vector_0_0, 0), - (self.blocks_multiply_const_vxx_0_0_0, 0), - ) - self.connect( - (self.blocks_stream_to_vector_0_1, 0), - (self.blocks_multiply_const_vxx_0_0, 0), - ) - self.connect( - (self.blocks_stream_to_vector_0_2, 0), - (self.blocks_multiply_const_vxx_0_0_0_0, 0), - ) - self.connect((self.blocks_tags_strobe_0, 0), - (self.blocks_add_xx_0_0, 0)) - self.connect((self.blocks_tags_strobe_0_0, 0), - (self.blocks_add_xx_0_0, 2)) + self.connect((self.blocks_skiphead_0, 0), (self.blocks_stream_to_vector_0, 0)) + self.connect((self.blocks_stream_to_vector_0, 0), (self.blocks_multiply_const_vxx_0, 0)) + self.connect((self.blocks_stream_to_vector_0_0, 0), (self.blocks_multiply_const_vxx_0_0_0, 0)) + self.connect((self.blocks_stream_to_vector_0_1, 0), (self.blocks_multiply_const_vxx_0_0, 0)) + self.connect((self.blocks_stream_to_vector_0_2, 0), (self.blocks_multiply_const_vxx_0_0_0_0, 0)) + self.connect((self.blocks_tags_strobe_0, 0), (self.blocks_add_xx_0_0, 0)) + self.connect((self.blocks_tags_strobe_0_0, 0), (self.blocks_add_xx_0_0, 2)) self.connect((self.dc_blocker_xx_0, 0), (self.blocks_skiphead_0, 0)) - self.connect((self.fft_vxx_0, 0), - (self.blocks_complex_to_mag_squared_0, 0)) - self.connect((self.osmosdr_source_0, 0), (self.add_clock_tags, 0)) + self.connect((self.fft_vxx_0, 0), (self.blocks_complex_to_mag_squared_0, 0)) + self.connect((self.uhd_usrp_source_1, 0), (self.add_clock_tags, 0)) + def get_num_bins(self): return self.num_bins @@ -282,181 +203,74 @@ def get_num_bins(self): def set_num_bins(self, num_bins): self.num_bins = num_bins self.set_cal_values(np.repeat(np.nan, self.num_bins)) - self.set_custom_window(self.sinc_samples * - np.hamming(4 * self.num_bins)) + self.set_custom_window(self.sinc_samples*np.hamming(4*self.num_bins)) self.set_fft_window(window.blackmanharris(self.num_bins)) - self.set_sinc_sample_locations( - np.arange(-np.pi * 4 / 2.0, np.pi * 4 / 2.0, np.pi / self.num_bins) - ) - self.set_tag_period(self.num_bins * self.num_integrations) - self.blocks_delay_0.set_dly(self.num_bins * 3) - self.blocks_delay_0_0.set_dly(self.num_bins * 2) - self.blocks_delay_0_1.set_dly(self.num_bins) - self.blocks_multiply_const_vxx_0.set_k( - self.custom_window[-self.num_bins:]) - self.blocks_multiply_const_vxx_0_0.set_k( - self.custom_window[2 * self.num_bins: 3 * self.num_bins] - ) - self.blocks_multiply_const_vxx_0_0_0.set_k( - self.custom_window[self.num_bins: 2 * self.num_bins] - ) - self.blocks_multiply_const_vxx_0_0_0_0.set_k( - self.custom_window[0: self.num_bins] - ) - self.blocks_tags_strobe_0_0.set_value( - pmt.to_pmt( - { - "num_bins": self.num_bins, - "samp_rate": self.samp_rate, - "num_integrations": self.num_integrations, - "motor_az": self.motor_az, - "motor_el": self.motor_el, - "freq": self.freq, - "tsys": self.tsys, - "tcal": self.tcal, - "cal_pwr": self.cal_pwr, - "vlsr": self.vlsr, - "glat": self.glat, - "glon": self.glon, - "soutrack": self.soutrack, - "bsw": self.beam_switch, - } - ) - ) + self.set_sinc_sample_locations(np.arange(-np.pi*4/2.0, np.pi*4/2.0, np.pi/self.num_bins)) + self.set_tag_period(self.num_bins*self.num_integrations) + self.blocks_delay_0.set_dly(int((self.num_bins*3))) + self.blocks_delay_0_0.set_dly(int((self.num_bins*2))) + self.blocks_delay_0_1.set_dly(int(self.num_bins)) + self.blocks_multiply_const_vxx_0.set_k(self.custom_window[-self.num_bins:]) + self.blocks_multiply_const_vxx_0_0.set_k(self.custom_window[2*self.num_bins:3*self.num_bins]) + self.blocks_multiply_const_vxx_0_0_0.set_k(self.custom_window[self.num_bins:2*self.num_bins]) + self.blocks_multiply_const_vxx_0_0_0_0.set_k(self.custom_window[0:self.num_bins]) + self.blocks_tags_strobe_0_0.set_value(pmt.to_pmt({"num_bins": self.num_bins, "samp_rate": self.samp_rate, "num_integrations": self.num_integrations, "motor_az": self.motor_az, "motor_el": self.motor_el, "freq": self.freq, "tsys": self.tsys, "tcal": self.tcal, "cal_pwr": self.cal_pwr, "vlsr": self.vlsr, "glat": self.glat, "glon": self.glon, "soutrack": self.soutrack, "bsw": self.beam_switch, "cal_on":self.cal_on})) + def get_num_integrations(self): return self.num_integrations def set_num_integrations(self, num_integrations): self.num_integrations = num_integrations - self.set_tag_period(self.num_bins * self.num_integrations) - self.blocks_multiply_const_xx_0.set_k( - 1.0 / float(self.num_integrations)) - self.blocks_tags_strobe_0_0.set_value( - pmt.to_pmt( - { - "num_bins": self.num_bins, - "samp_rate": self.samp_rate, - "num_integrations": self.num_integrations, - "motor_az": self.motor_az, - "motor_el": self.motor_el, - "freq": self.freq, - "tsys": self.tsys, - "tcal": self.tcal, - "cal_pwr": self.cal_pwr, - "vlsr": self.vlsr, - "glat": self.glat, - "glon": self.glon, - "soutrack": self.soutrack, - "bsw": self.beam_switch, - } - ) - ) + self.set_tag_period(self.num_bins*self.num_integrations) + self.blocks_multiply_const_xx_0.set_k(1.0/float(self.num_integrations)) + self.blocks_tags_strobe_0_0.set_value(pmt.to_pmt({"num_bins": self.num_bins, "samp_rate": self.samp_rate, "num_integrations": self.num_integrations, "motor_az": self.motor_az, "motor_el": self.motor_el, "freq": self.freq, "tsys": self.tsys, "tcal": self.tcal, "cal_pwr": self.cal_pwr, "vlsr": self.vlsr, "glat": self.glat, "glon": self.glon, "soutrack": self.soutrack, "bsw": self.beam_switch, "cal_on":self.cal_on})) def get_sinc_sample_locations(self): return self.sinc_sample_locations def set_sinc_sample_locations(self, sinc_sample_locations): self.sinc_sample_locations = sinc_sample_locations - self.set_sinc_samples(np.sinc(self.sinc_sample_locations / np.pi)) + self.set_sinc_samples(np.sinc(self.sinc_sample_locations/np.pi)) def get_sinc_samples(self): return self.sinc_samples def set_sinc_samples(self, sinc_samples): self.sinc_samples = sinc_samples - self.set_custom_window(self.sinc_samples * - np.hamming(4 * self.num_bins)) + self.set_custom_window(self.sinc_samples*np.hamming(4*self.num_bins)) + + def get_freq(self): + return self.freq + + def set_freq(self, freq): + self.freq = freq + self.set_rf_freq(self.freq) + self.blocks_tags_strobe_0.set_value(pmt.to_pmt(float(self.freq))) + self.blocks_tags_strobe_0_0.set_value(pmt.to_pmt({"num_bins": self.num_bins, "samp_rate": self.samp_rate, "num_integrations": self.num_integrations, "motor_az": self.motor_az, "motor_el": self.motor_el, "freq": self.freq, "tsys": self.tsys, "tcal": self.tcal, "cal_pwr": self.cal_pwr, "vlsr": self.vlsr, "glat": self.glat, "glon": self.glon, "soutrack": self.soutrack, "bsw": self.beam_switch, "cal_on":self.cal_on})) def get_vlsr(self): return self.vlsr def set_vlsr(self, vlsr): self.vlsr = vlsr - self.blocks_tags_strobe_0_0.set_value( - pmt.to_pmt( - { - "num_bins": self.num_bins, - "samp_rate": self.samp_rate, - "num_integrations": self.num_integrations, - "motor_az": self.motor_az, - "motor_el": self.motor_el, - "freq": self.freq, - "tsys": self.tsys, - "tcal": self.tcal, - "cal_pwr": self.cal_pwr, - "vlsr": self.vlsr, - "glat": self.glat, - "glon": self.glon, - "soutrack": self.soutrack, - "bsw": self.beam_switch, - } - ) - ) + self.blocks_tags_strobe_0_0.set_value(pmt.to_pmt({"num_bins": self.num_bins, "samp_rate": self.samp_rate, "num_integrations": self.num_integrations, "motor_az": self.motor_az, "motor_el": self.motor_el, "freq": self.freq, "tsys": self.tsys, "tcal": self.tcal, "cal_pwr": self.cal_pwr, "vlsr": self.vlsr, "glat": self.glat, "glon": self.glon, "soutrack": self.soutrack, "bsw": self.beam_switch, "cal_on":self.cal_on})) def get_tsys(self): return self.tsys def set_tsys(self, tsys): self.tsys = tsys - self.blocks_multiply_const_vxx_1.set_k( - [ - (self.tsys + self.tcal) / (value * self.cal_pwr) - for value in self.cal_values - ] - ) - self.blocks_tags_strobe_0_0.set_value( - pmt.to_pmt( - { - "num_bins": self.num_bins, - "samp_rate": self.samp_rate, - "num_integrations": self.num_integrations, - "motor_az": self.motor_az, - "motor_el": self.motor_el, - "freq": self.freq, - "tsys": self.tsys, - "tcal": self.tcal, - "cal_pwr": self.cal_pwr, - "vlsr": self.vlsr, - "glat": self.glat, - "glon": self.glon, - "soutrack": self.soutrack, - "bsw": self.beam_switch, - } - ) - ) + self.blocks_multiply_const_vxx_1.set_k([(self.tsys + self.tcal)/(value * self.cal_pwr) for value in self.cal_values]) + self.blocks_tags_strobe_0_0.set_value(pmt.to_pmt({"num_bins": self.num_bins, "samp_rate": self.samp_rate, "num_integrations": self.num_integrations, "motor_az": self.motor_az, "motor_el": self.motor_el, "freq": self.freq, "tsys": self.tsys, "tcal": self.tcal, "cal_pwr": self.cal_pwr, "vlsr": self.vlsr, "glat": self.glat, "glon": self.glon, "soutrack": self.soutrack, "bsw": self.beam_switch, "cal_on":self.cal_on})) def get_tcal(self): return self.tcal def set_tcal(self, tcal): self.tcal = tcal - self.blocks_multiply_const_vxx_1.set_k( - [ - (self.tsys + self.tcal) / (value * self.cal_pwr) - for value in self.cal_values - ] - ) - self.blocks_tags_strobe_0_0.set_value( - pmt.to_pmt( - { - "num_bins": self.num_bins, - "samp_rate": self.samp_rate, - "num_integrations": self.num_integrations, - "motor_az": self.motor_az, - "motor_el": self.motor_el, - "freq": self.freq, - "tsys": self.tsys, - "tcal": self.tcal, - "cal_pwr": self.cal_pwr, - "vlsr": self.vlsr, - "glat": self.glat, - "glon": self.glon, - "soutrack": self.soutrack, - "bsw": self.beam_switch, - } - ) - ) + self.blocks_multiply_const_vxx_1.set_k([(self.tsys + self.tcal)/(value * self.cal_pwr) for value in self.cal_values]) + self.blocks_tags_strobe_0_0.set_value(pmt.to_pmt({"num_bins": self.num_bins, "samp_rate": self.samp_rate, "num_integrations": self.num_integrations, "motor_az": self.motor_az, "motor_el": self.motor_el, "freq": self.freq, "tsys": self.tsys, "tcal": self.tcal, "cal_pwr": self.cal_pwr, "vlsr": self.vlsr, "glat": self.glat, "glon": self.glon, "soutrack": self.soutrack, "bsw": self.beam_switch, "cal_on":self.cal_on})) def get_tag_period(self): return self.tag_period @@ -472,105 +286,46 @@ def get_soutrack(self): def set_soutrack(self, soutrack): self.soutrack = soutrack - self.blocks_tags_strobe_0_0.set_value( - pmt.to_pmt( - { - "num_bins": self.num_bins, - "samp_rate": self.samp_rate, - "num_integrations": self.num_integrations, - "motor_az": self.motor_az, - "motor_el": self.motor_el, - "freq": self.freq, - "tsys": self.tsys, - "tcal": self.tcal, - "cal_pwr": self.cal_pwr, - "vlsr": self.vlsr, - "glat": self.glat, - "glon": self.glon, - "soutrack": self.soutrack, - "bsw": self.beam_switch, - } - ) - ) + self.blocks_tags_strobe_0_0.set_value(pmt.to_pmt({"num_bins": self.num_bins, "samp_rate": self.samp_rate, "num_integrations": self.num_integrations, "motor_az": self.motor_az, "motor_el": self.motor_el, "freq": self.freq, "tsys": self.tsys, "tcal": self.tcal, "cal_pwr": self.cal_pwr, "vlsr": self.vlsr, "glat": self.glat, "glon": self.glon, "soutrack": self.soutrack, "bsw": self.beam_switch, "cal_on":self.cal_on})) def get_samp_rate(self): return self.samp_rate def set_samp_rate(self, samp_rate): self.samp_rate = samp_rate - self.blocks_tags_strobe_0_0.set_value( - pmt.to_pmt( - { - "num_bins": self.num_bins, - "samp_rate": self.samp_rate, - "num_integrations": self.num_integrations, - "motor_az": self.motor_az, - "motor_el": self.motor_el, - "freq": self.freq, - "tsys": self.tsys, - "tcal": self.tcal, - "cal_pwr": self.cal_pwr, - "vlsr": self.vlsr, - "glat": self.glat, - "glon": self.glon, - "soutrack": self.soutrack, - "bsw": self.beam_switch, - } - ) - ) - self.osmosdr_source_0.set_sample_rate(self.samp_rate) + self.blocks_tags_strobe_0_0.set_value(pmt.to_pmt({"num_bins": self.num_bins, "samp_rate": self.samp_rate, "num_integrations": self.num_integrations, "motor_az": self.motor_az, "motor_el": self.motor_el, "freq": self.freq, "tsys": self.tsys, "tcal": self.tcal, "cal_pwr": self.cal_pwr, "vlsr": self.vlsr, "glat": self.glat, "glon": self.glon, "soutrack": self.soutrack, "bsw": self.beam_switch, "cal_on":self.cal_on})) + self.uhd_usrp_source_1.set_samp_rate(self.samp_rate) + self.uhd_usrp_source_1.set_bandwidth(self.samp_rate, 0) + self.uhd_usrp_source_1.set_center_freq(uhd.tune_request(self.rf_freq,self.samp_rate*0.6), 0) + + def get_rf_gain(self): + return self.rf_gain + + def set_rf_gain(self, rf_gain): + self.rf_gain = rf_gain + self.uhd_usrp_source_1.set_gain(self.rf_gain, 0) + + def get_rf_freq(self): + return self.rf_freq + + def set_rf_freq(self, rf_freq): + self.rf_freq = rf_freq + self.uhd_usrp_source_1.set_center_freq(uhd.tune_request(self.rf_freq,self.samp_rate*0.6), 0) + #self.uhd_usrp_source_1.set_center_freq(rf_freq, 0) def get_motor_el(self): return self.motor_el def set_motor_el(self, motor_el): self.motor_el = motor_el - self.blocks_tags_strobe_0_0.set_value( - pmt.to_pmt( - { - "num_bins": self.num_bins, - "samp_rate": self.samp_rate, - "num_integrations": self.num_integrations, - "motor_az": self.motor_az, - "motor_el": self.motor_el, - "freq": self.freq, - "tsys": self.tsys, - "tcal": self.tcal, - "cal_pwr": self.cal_pwr, - "vlsr": self.vlsr, - "glat": self.glat, - "glon": self.glon, - "soutrack": self.soutrack, - "bsw": self.beam_switch, - } - ) - ) + self.blocks_tags_strobe_0_0.set_value(pmt.to_pmt({"num_bins": self.num_bins, "samp_rate": self.samp_rate, "num_integrations": self.num_integrations, "motor_az": self.motor_az, "motor_el": self.motor_el, "freq": self.freq, "tsys": self.tsys, "tcal": self.tcal, "cal_pwr": self.cal_pwr, "vlsr": self.vlsr, "glat": self.glat, "glon": self.glon, "soutrack": self.soutrack, "bsw": self.beam_switch, "cal_on":self.cal_on})) def get_motor_az(self): return self.motor_az def set_motor_az(self, motor_az): self.motor_az = motor_az - self.blocks_tags_strobe_0_0.set_value( - pmt.to_pmt( - { - "num_bins": self.num_bins, - "samp_rate": self.samp_rate, - "num_integrations": self.num_integrations, - "motor_az": self.motor_az, - "motor_el": self.motor_el, - "freq": self.freq, - "tsys": self.tsys, - "tcal": self.tcal, - "cal_pwr": self.cal_pwr, - "vlsr": self.vlsr, - "glat": self.glat, - "glon": self.glon, - "soutrack": self.soutrack, - "bsw": self.beam_switch, - } - ) - ) + self.blocks_tags_strobe_0_0.set_value(pmt.to_pmt({"num_bins": self.num_bins, "samp_rate": self.samp_rate, "num_integrations": self.num_integrations, "motor_az": self.motor_az, "motor_el": self.motor_el, "freq": self.freq, "tsys": self.tsys, "tcal": self.tcal, "cal_pwr": self.cal_pwr, "vlsr": self.vlsr, "glat": self.glat, "glon": self.glon, "soutrack": self.soutrack, "bsw": self.beam_switch, "cal_on":self.cal_on})) def get_is_running(self): return self.is_running @@ -584,80 +339,14 @@ def get_glon(self): def set_glon(self, glon): self.glon = glon - self.blocks_tags_strobe_0_0.set_value( - pmt.to_pmt( - { - "num_bins": self.num_bins, - "samp_rate": self.samp_rate, - "num_integrations": self.num_integrations, - "motor_az": self.motor_az, - "motor_el": self.motor_el, - "freq": self.freq, - "tsys": self.tsys, - "tcal": self.tcal, - "cal_pwr": self.cal_pwr, - "vlsr": self.vlsr, - "glat": self.glat, - "glon": self.glon, - "soutrack": self.soutrack, - "bsw": self.beam_switch, - } - ) - ) + self.blocks_tags_strobe_0_0.set_value(pmt.to_pmt({"num_bins": self.num_bins, "samp_rate": self.samp_rate, "num_integrations": self.num_integrations, "motor_az": self.motor_az, "motor_el": self.motor_el, "freq": self.freq, "tsys": self.tsys, "tcal": self.tcal, "cal_pwr": self.cal_pwr, "vlsr": self.vlsr, "glat": self.glat, "glon": self.glon, "soutrack": self.soutrack, "bsw": self.beam_switch, "cal_on":self.cal_on})) def get_glat(self): return self.glat def set_glat(self, glat): self.glat = glat - self.blocks_tags_strobe_0_0.set_value( - pmt.to_pmt( - { - "num_bins": self.num_bins, - "samp_rate": self.samp_rate, - "num_integrations": self.num_integrations, - "motor_az": self.motor_az, - "motor_el": self.motor_el, - "freq": self.freq, - "tsys": self.tsys, - "tcal": self.tcal, - "cal_pwr": self.cal_pwr, - "vlsr": self.vlsr, - "glat": self.glat, - "glon": self.glon, - "soutrack": self.soutrack, - "bsw": self.beam_switch, - } - ) - ) - - def get_freq(self): - return self.freq - - def set_freq(self, freq): - self.freq = freq - self.blocks_tags_strobe_0.set_value(pmt.to_pmt(float(self.freq))) - self.blocks_tags_strobe_0_0.set_value( - pmt.to_pmt( - { - "num_bins": self.num_bins, - "samp_rate": self.samp_rate, - "num_integrations": self.num_integrations, - "motor_az": self.motor_az, - "motor_el": self.motor_el, - "freq": self.freq, - "tsys": self.tsys, - "tcal": self.tcal, - "cal_pwr": self.cal_pwr, - "vlsr": self.vlsr, - "glat": self.glat, - "glon": self.glon, - "soutrack": self.soutrack, - "bsw": self.beam_switch, - } - ) - ) - self.osmosdr_source_0.set_center_freq(self.freq, 0) + self.blocks_tags_strobe_0_0.set_value(pmt.to_pmt({"num_bins": self.num_bins, "samp_rate": self.samp_rate, "num_integrations": self.num_integrations, "motor_az": self.motor_az, "motor_el": self.motor_el, "freq": self.freq, "tsys": self.tsys, "tcal": self.tcal, "cal_pwr": self.cal_pwr, "vlsr": self.vlsr, "glat": self.glat, "glon": self.glon, "soutrack": self.soutrack, "bsw": self.beam_switch, "cal_on":self.cal_on})) def get_fft_window(self): return self.fft_window @@ -670,115 +359,65 @@ def get_custom_window(self): def set_custom_window(self, custom_window): self.custom_window = custom_window - self.blocks_multiply_const_vxx_0.set_k( - self.custom_window[-self.num_bins:]) - self.blocks_multiply_const_vxx_0_0.set_k( - self.custom_window[2 * self.num_bins: 3 * self.num_bins] - ) - self.blocks_multiply_const_vxx_0_0_0.set_k( - self.custom_window[self.num_bins: 2 * self.num_bins] - ) - self.blocks_multiply_const_vxx_0_0_0_0.set_k( - self.custom_window[0: self.num_bins] - ) + self.blocks_multiply_const_vxx_0.set_k(self.custom_window[-self.num_bins:]) + self.blocks_multiply_const_vxx_0_0.set_k(self.custom_window[2*self.num_bins:3*self.num_bins]) + self.blocks_multiply_const_vxx_0_0_0.set_k(self.custom_window[self.num_bins:2*self.num_bins]) + self.blocks_multiply_const_vxx_0_0_0_0.set_k(self.custom_window[0:self.num_bins]) + + def get_calibrator_mask(self): + return self.calibrator_mask + + def set_calibrator_mask(self, calibrator_mask): + self.calibrator_mask = calibrator_mask + self.calibrator_control_strobe.calibrator_mask = self.calibrator_mask def get_cal_values(self): return self.cal_values def set_cal_values(self, cal_values): self.cal_values = cal_values - self.blocks_multiply_const_vxx_1.set_k( - [ - (self.tsys + self.tcal) / (value * self.cal_pwr) - for value in self.cal_values - ] - ) + self.blocks_multiply_const_vxx_1.set_k([(self.tsys + self.tcal)/(value * self.cal_pwr) for value in self.cal_values]) def get_cal_pwr(self): return self.cal_pwr def set_cal_pwr(self, cal_pwr): self.cal_pwr = cal_pwr - self.blocks_multiply_const_vxx_1.set_k( - [ - (self.tsys + self.tcal) / (value * self.cal_pwr) - for value in self.cal_values - ] - ) - self.blocks_tags_strobe_0_0.set_value( - pmt.to_pmt( - { - "num_bins": self.num_bins, - "samp_rate": self.samp_rate, - "num_integrations": self.num_integrations, - "motor_az": self.motor_az, - "motor_el": self.motor_el, - "freq": self.freq, - "tsys": self.tsys, - "tcal": self.tcal, - "cal_pwr": self.cal_pwr, - "vlsr": self.vlsr, - "glat": self.glat, - "glon": self.glon, - "soutrack": self.soutrack, - "bsw": self.beam_switch, - } - ) - ) + self.blocks_multiply_const_vxx_1.set_k([(self.tsys + self.tcal)/(value * self.cal_pwr) for value in self.cal_values]) + self.blocks_tags_strobe_0_0.set_value(pmt.to_pmt({"num_bins": self.num_bins, "samp_rate": self.samp_rate, "num_integrations": self.num_integrations, "motor_az": self.motor_az, "motor_el": self.motor_el, "freq": self.freq, "tsys": self.tsys, "tcal": self.tcal, "cal_pwr": self.cal_pwr, "vlsr": self.vlsr, "glat": self.glat, "glon": self.glon, "soutrack": self.soutrack, "bsw": self.beam_switch, "cal_on":self.cal_on})) + + def get_cal_on(self): + return self.cal_on + + def set_cal_on(self, cal_on): + self.cal_on = cal_on + self.blocks_tags_strobe_0_0.set_value(pmt.to_pmt({"num_bins": self.num_bins, "samp_rate": self.samp_rate, "num_integrations": self.num_integrations, "motor_az": self.motor_az, "motor_el": self.motor_el, "freq": self.freq, "tsys": self.tsys, "tcal": self.tcal, "cal_pwr": self.cal_pwr, "vlsr": self.vlsr, "glat": self.glat, "glon": self.glon, "soutrack": self.soutrack, "bsw": self.beam_switch, "cal_on":self.cal_on})) + self.calibrator_control_strobe.cal_state = self.cal_on def get_beam_switch(self): return self.beam_switch def set_beam_switch(self, beam_switch): self.beam_switch = beam_switch - self.blocks_tags_strobe_0_0.set_value( - pmt.to_pmt( - { - "num_bins": self.num_bins, - "samp_rate": self.samp_rate, - "num_integrations": self.num_integrations, - "motor_az": self.motor_az, - "motor_el": self.motor_el, - "freq": self.freq, - "tsys": self.tsys, - "tcal": self.tcal, - "cal_pwr": self.cal_pwr, - "vlsr": self.vlsr, - "glat": self.glat, - "glon": self.glon, - "soutrack": self.soutrack, - "bsw": self.beam_switch, - } - ) - ) + self.blocks_tags_strobe_0_0.set_value(pmt.to_pmt({"num_bins": self.num_bins, "samp_rate": self.samp_rate, "num_integrations": self.num_integrations, "motor_az": self.motor_az, "motor_el": self.motor_el, "freq": self.freq, "tsys": self.tsys, "tcal": self.tcal, "cal_pwr": self.cal_pwr, "vlsr": self.vlsr, "glat": self.glat, "glon": self.glon, "soutrack": self.soutrack, "bsw": self.beam_switch, "cal_on":self.cal_on})) def argument_parser(): parser = ArgumentParser() parser.add_argument( - "--num-bins", - dest="num_bins", - type=intx, - default=256, - help="Set num_bins [default=%(default)r]", - ) + "--num-bins", dest="num_bins", type=intx, default=256, + help="Set num_bins [default=%(default)r]") parser.add_argument( - "--num-integrations", - dest="num_integrations", - type=intx, - default=100000, - help="Set num_integrations [default=%(default)r]", - ) + "--num-integrations", dest="num_integrations", type=intx, default=100000, + help="Set num_integrations [default=%(default)r]") return parser def main(top_block_cls=radio_process, options=None): if options is None: options = argument_parser().parse_args() - tb = top_block_cls( - num_bins=options.num_bins, num_integrations=options.num_integrations - ) - + tb = top_block_cls(num_bins=options.num_bins, num_integrations=options.num_integrations) + def sig_handler(sig=None, frame=None): tb.stop() tb.wait() @@ -793,5 +432,5 @@ def sig_handler(sig=None, frame=None): tb.wait() -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/srt/daemon/radio_control/radio_save_raw/radio_save_raw.py b/srt/daemon/radio_control/radio_save_raw/radio_save_raw.py index cf1c1ad7..6867306f 100644 --- a/srt/daemon/radio_control/radio_save_raw/radio_save_raw.py +++ b/srt/daemon/radio_control/radio_save_raw/radio_save_raw.py @@ -21,8 +21,9 @@ class radio_save_raw(gr.top_block): + def __init__(self, directory_name="./rf_data", samp_rate=2400000): - gr.top_block.__init__(self, "radio_save_raw") + gr.top_block.__init__(self, "radio_save_raw", catch_exceptions=True) ################################################## # Parameters @@ -33,42 +34,43 @@ def __init__(self, directory_name="./rf_data", samp_rate=2400000): ################################################## # Blocks ################################################## - self.zeromq_sub_source_0 = zeromq.sub_source( - gr.sizeof_gr_complex, 1, "tcp://127.0.0.1:5558", 100, True, -1 - ) - self.gr_digital_rf_digital_rf_channel_sink_0 = ( - gr_digital_rf.digital_rf_channel_sink( - channel_dir=directory_name, - dtype=np.complex64, - subdir_cadence_secs=3600, - file_cadence_millisecs=1000, - sample_rate_numerator=int(samp_rate), - sample_rate_denominator=1, - start="now", - ignore_tags=False, - is_complex=True, - num_subchannels=1, - uuid_str=None, - center_frequencies=[], - metadata={}, - is_continuous=True, - compression_level=0, - checksum=False, - marching_periods=True, - stop_on_skipped=False, - stop_on_time_tag=False, - debug=False, - min_chunksize=None, - ) + + self.zeromq_sub_source_0 = zeromq.sub_source(gr.sizeof_gr_complex, 1, 'tcp://127.0.0.1:5558', 100, True, (-1), '', False) + self.gr_digital_rf_digital_rf_sink_0 = gr_digital_rf.digital_rf_sink( + directory_name, + channels=[ + 'ch0', + ], + dtype=np.complex64, + subdir_cadence_secs=3600, + file_cadence_millisecs=1000, + sample_rate_numerator=int(samp_rate), + sample_rate_denominator=1, + start='now', + ignore_tags=False, + is_complex=True, + num_subchannels=1, + uuid_str=None, + center_frequencies=( + None + ), + metadata={}, + is_continuous=True, + compression_level=0, + checksum=False, + marching_periods=True, + stop_on_skipped=False, + stop_on_time_tag=False, + debug=False, + min_chunksize=None, ) + ################################################## # Connections ################################################## - self.connect( - (self.zeromq_sub_source_0, 0), - (self.gr_digital_rf_digital_rf_channel_sink_0, 0), - ) + self.connect((self.zeromq_sub_source_0, 0), (self.gr_digital_rf_digital_rf_sink_0, 0)) + def get_directory_name(self): return self.directory_name @@ -83,31 +85,22 @@ def set_samp_rate(self, samp_rate): self.samp_rate = samp_rate + def argument_parser(): parser = ArgumentParser() parser.add_argument( - "--directory-name", - dest="directory_name", - type=str, - default="./rf_data", - help="Set ./rf_data [default=%(default)r]", - ) + "--directory-name", dest="directory_name", type=str, default="./rf_data", + help="Set ./rf_data [default=%(default)r]") parser.add_argument( - "--samp-rate", - dest="samp_rate", - type=intx, - default=2400000, - help="Set samp_rate [default=%(default)r]", - ) + "--samp-rate", dest="samp_rate", type=intx, default=2400000, + help="Set samp_rate [default=%(default)r]") return parser def main(top_block_cls=radio_save_raw, options=None): if options is None: options = argument_parser().parse_args() - tb = top_block_cls( - directory_name=options.directory_name, samp_rate=options.samp_rate - ) + tb = top_block_cls(directory_name=options.directory_name, samp_rate=options.samp_rate) def sig_handler(sig=None, frame=None): tb.stop() @@ -123,5 +116,5 @@ def sig_handler(sig=None, frame=None): tb.wait() -if __name__ == "__main__": - main() +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/srt/daemon/radio_control/radio_save_spec_rad/save_rad_file.py b/srt/daemon/radio_control/radio_save_spec_rad/save_rad_file.py index 09e7c19e..b26b2f9d 100644 --- a/srt/daemon/radio_control/radio_save_spec_rad/save_rad_file.py +++ b/srt/daemon/radio_control/radio_save_spec_rad/save_rad_file.py @@ -154,6 +154,7 @@ def work(self, input_items, output_items): glat, glon, soutrack, + ) start_line = start_format % ( istart * bw / nfreq + efflofreq, diff --git a/srt/daemon/rotor_control/bigdish_client.py b/srt/daemon/rotor_control/bigdish_client.py new file mode 100755 index 00000000..149595c3 --- /dev/null +++ b/srt/daemon/rotor_control/bigdish_client.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 + +import time +import json +import threading +from websockets.sync.client import connect + +class BigDishClient: + def __init__(self, server_host, server_port, user, password, kick_others = False): + self.websocket = connect(f"ws://{server_host}:{server_port}") + self.message_id = 0 + self.websocket.send(json.dumps({"type": "auth", "id": self.message_id, "user": user, "password": password, "version": "0.0.1"})) + self.message_id += 1 + self.websocket.send(json.dumps({"type": "init", "id": self.message_id, "kick_others": kick_others})) + self.message_id += 1 + self.received_messages = {} + self._message_recv_thread_handle = threading.Thread(target = self._message_recv_thread) + self._message_recv_thread_handle.start() + + def _message_recv_thread(self): + for message in self.websocket: + message_decoded = json.loads(message) + self.received_messages[message_decoded["id"]] = message_decoded + + def _wait_for_response(self, id): + while True: + if id in self.received_messages: + message = self.received_messages[id] + del self.received_messages[id] + self.message_id += 1 + return message + time.sleep(0.01) + + def stow_pos(self): + self.websocket.send(json.dumps({"type": "stow_pos", "id": self.message_id})) + return self._wait_for_response(self.message_id) + + def goto_posvel_azel(self, az_pos, el_pos, az_vel, el_vel): + self.websocket.send(json.dumps({"type": "goto_posvel", "id": self.message_id, "coords": "azel", "az_pos": az_pos, "az_vel": az_vel, "el_pos": el_pos, "el_vel": el_vel})) + return self._wait_for_response(self.message_id) + + def track_radec(self, ra_pos, dec_pos, duration): + self.websocket.send(json.dumps({"type": "track", "id": self.message_id, "coords": "radec", "ra_pos": ra_pos, "dec_pos": dec_pos, "duration": duration})) + return self._wait_for_response(self.message_id) + + def track_gal(self, l_pos, b_pos, duration): + self.websocket.send(json.dumps({"type": "track", "id": self.message_id, "coords": "gal", "l_pos": l_pos, "b_pos": b_pos, "duration": duration})) + return self._wait_for_response(self.message_id) + + def get_posvel(self, coords, power): + self.websocket.send(json.dumps({"type": "get_posvel", "id": self.message_id, "coords": coords, "power": power})) + return self._wait_for_response(self.message_id) + +if __name__ == "__main__": + client = BigDishClient("localhost", 1234, "w1xm", "test", kick_others = True) + #while True: + # client.track_gal(162.592,4.5697, 5) + # time.sleep(1) diff --git a/srt/daemon/rotor_control/motors.py b/srt/daemon/rotor_control/motors.py index 24dddb0b..2fd5ccca 100644 --- a/srt/daemon/rotor_control/motors.py +++ b/srt/daemon/rotor_control/motors.py @@ -9,6 +9,7 @@ from time import sleep from math import cos, acos, pi, sqrt, floor +from .bigdish_client import BigDishClient class Motor(ABC): """Abstract Class for All Motors Types @@ -295,6 +296,7 @@ def point(self, az, el): az_relative = az - self.az_limits[0] el_relative = el - self.el_limits[0] self.send_rot2_pkt(cmd, az=az_relative, el=el_relative) + sleep(0.3) def status(self): """Requests the Current Location of the ROT2 Motor @@ -783,6 +785,7 @@ def point(self, az, el): None """ self.send_pushrod_cmd(az, el, 0) + sleep(0.5) def status(self): """Requests the Current Location of the Pushrod Motor @@ -793,3 +796,46 @@ def status(self): Current Azimuth and Elevation Coordinate as a Tuple of Floats """ return self.az, self.el + +class W1XMBigDishMotor(Motor): + """ + Class for Controlling the 54 roof Big Dish + """ + + def __init__(self): + """ + Initializer for W1XM Big Dish controller + """ + super().__init__(None, None, (0.0, 360.0), (0.0, 85.0)) + self.position = (60.0, 30.0) + self.client = BigDishClient("172.25.15.11", 1234, "w1xm", "test", True) + + def point(self, az, el): + """Points the dish at a point + + Parameters + ---------- + az : float + Azimuth Coordinate to Point At + el : float + Elevation Coordinate to Point At + + Returns + ------- + None + """ + self.client.goto_posvel_azel(az, el, 0.0, 0.0) + sleep(0.01) + #self.position = (az, el) + + def status(self): + """Returns the Position of the Dish + + Returns + ------- + (float, float) + Current Azimuth and Elevation Coordinate as a Tuple of Floats + """ + pos = self.client.get_posvel("azel", False) + self.position = (pos["az_pos"], pos["el_pos"]) + return self.position diff --git a/srt/daemon/rotor_control/rotors.py b/srt/daemon/rotor_control/rotors.py index 04caaf0c..8839acf8 100644 --- a/srt/daemon/rotor_control/rotors.py +++ b/srt/daemon/rotor_control/rotors.py @@ -5,8 +5,7 @@ """ from enum import Enum -from .motors import NoMotor, Rot2Motor, H180Motor, PushRodMotor - +from .motors import NoMotor, Rot2Motor, H180Motor, PushRodMotor, W1XMBigDishMotor def angle_within_range(angle, limits): lower_limit, upper_limit = limits @@ -25,6 +24,7 @@ class RotorType(Enum): ROT2 = "ALFASPID" H180 = "H180MOUNT" PUSH_ROD = "PUSHROD" + W1XM_BIG_DISH = "W1XMBIGDISH" class Rotor: @@ -37,7 +37,8 @@ class Rotor: """ def __init__(self, motor_type, port, baudrate, az_limits, el_limits): - """Initializes the Rotor with its Motor Object + """Initializes the Rotor with its Motor Object and defines + fixed parameters needed for control and settling checks Parameters ---------- @@ -50,14 +51,27 @@ def __init__(self, motor_type, port, baudrate, az_limits, el_limits): el_limits : (float, float) Tuple of Lower and Upper Elevation Limits """ + if motor_type == RotorType.NONE or motor_type == RotorType.NONE.value: self.motor = NoMotor(port, baudrate, az_limits, el_limits) + self.rotor_loop_cadence = 0.5 + self.pointing_accuracy = 1.0 elif motor_type == RotorType.ROT2 or motor_type == RotorType.ROT2.value: self.motor = Rot2Motor(port, baudrate, az_limits, el_limits) + self.rotor_loop_cadence = 0.5 + self.pointing_accuracy = 0.6 elif motor_type == RotorType.H180 or motor_type == RotorType.H180.value: self.motor = H180Motor(port, baudrate, az_limits, el_limits) + self.rotor_loop_cadence = 0.5 + self.pointing_accuracy = 0.6 elif motor_type == RotorType.PUSH_ROD == RotorType.PUSH_ROD.value: self.motor = PushRodMotor(port, baudrate, az_limits, el_limits) + self.rotor_loop_cadence = 0.5 + self.pointing_accuracy = 0.6 + elif motor_type == RotorType.W1XM_BIG_DISH or motor_type == RotorType.W1XM_BIG_DISH.value: + self.motor = W1XMBigDishMotor() + self.rotor_loop_cadence = 0.1 + self.pointing_accuracy = 0.1 else: raise ValueError("Not a known motor type") diff --git a/srt/daemon/utilities/calibration_functions.py b/srt/daemon/utilities/calibration_functions.py new file mode 100644 index 00000000..97256ad7 --- /dev/null +++ b/srt/daemon/utilities/calibration_functions.py @@ -0,0 +1,72 @@ +"""calibration_functions.py + +dsheen 2024/09/18 +Calibration Mathhematics for new SRT calibration scheme + +reimplements basic cold sky cal implemented directly in gnuradio +and adds additional capabilities for more advanced telescopes. +""" + +import numpy as np +import numpy.polynomial.polynomial as poly +from astropy.io import fits + +def get_averaged_spectrum(fits_file): + """ + open fits file and average all included spectra together + """ + spectrum_file = fits.open(fits_file) + average_spectrum = np.zeros(len(spectrum_file[0].data),dtype=np.float64) + + num_spectra = len(spectrum_file) + for i in range(0,num_spectra): + spectrum=spectrum_file[i] + average_spectrum += spectrum.data + + average_spectrum /= num_spectra + + return average_spectrum + + +def basic_cold_sky_calibration_fit(cold_sky_reference_filepath, t_sys=300, t_cal=300, polynomial_order=20): + """ + very basic calibration for single point temperature reference measurement. + calculates a polynomial fit for the spectrum and appropriately normalizes it + """ + + average_cold_sky_spectrum = get_averaged_spectrum(cold_sky_reference_filepath) + relative_freq_values = np.linspace(-1, 1, len(average_cold_sky_spectrum)) + polynomial_fit = poly.Polynomial.fit(relative_freq_values, average_cold_sky_spectrum, polynomial_order,) + + smoothed_cold_sky_spectrum = polynomial_fit(relative_freq_values) + average_value = np.average(smoothed_cold_sky_spectrum) + normalized_gain_spectrum = smoothed_cold_sky_spectrum/average_value + average_gain_correction = average_value/(t_sys+t_cal) + + + return normalized_gain_spectrum, average_gain_correction + + +def additive_noise_calibration_fit(cold_sky_reference_filepath, calibrator_reference_filepath, t_sys=300, t_cal=300, polynomial_order=20): + + average_cold_sky_spectrum = get_averaged_spectrum(cold_sky_reference_filepath) + average_calibrator_plus_sky_spectrum = get_averaged_spectrum(calibrator_reference_filepath) + average_calibrator_spectrum = average_calibrator_plus_sky_spectrum - average_cold_sky_spectrum + + relative_freq_values = np.linspace(-1, 1, len(average_cold_sky_spectrum)) + polynomial_fit = poly.Polynomial.fit(relative_freq_values, average_calibrator_spectrum, polynomial_order,) + + smoothed_calibrator_spectrum = polynomial_fit(relative_freq_values) + average_value = np.average(smoothed_calibrator_spectrum) + normalized_gain_spectrum = smoothed_calibrator_spectrum/average_value + average_gain_correction = average_value/t_cal + + return normalized_gain_spectrum, average_gain_correction + + + + + + + + diff --git a/srt/daemon/utilities/functions.py b/srt/daemon/utilities/functions.py index b044e4e3..4fa64b4d 100644 --- a/srt/daemon/utilities/functions.py +++ b/srt/daemon/utilities/functions.py @@ -7,7 +7,7 @@ import numpy as np -def angle_within_range(actual_angle, desired_angle, bounds=0.5): +def angle_within_range(actual_angle, desired_angle, bounds=0.1): """Determines if Angles are Within a Threshold of One Another Parameters @@ -24,10 +24,10 @@ def angle_within_range(actual_angle, desired_angle, bounds=0.5): bool Whether Angles Were Within Threshold """ - return abs(actual_angle - desired_angle) < bounds + return abs(actual_angle - desired_angle) % 360.0 <= bounds -def azel_within_range(actual_azel, desired_azel, bounds=(0.5, 0.5)): +def azel_within_range(actual_azel, desired_azel, bounds=(0.1, 0.1)): """Determines if AzEls are Within a Threshold of One Another Parameters diff --git a/srt/daemon/utilities/object_tracker.py b/srt/daemon/utilities/object_tracker.py index c58c5b82..a16908c4 100644 --- a/srt/daemon/utilities/object_tracker.py +++ b/srt/daemon/utilities/object_tracker.py @@ -3,8 +3,8 @@ Module for Tracking and Caching the Azimuth-Elevation Coords of Celestial Objects """ -from astropy.coordinates import SkyCoord, EarthLocation, get_sun, get_moon -from astropy.coordinates import ICRS, Galactic, FK4, CIRS, AltAz +from astropy.coordinates import SkyCoord, EarthLocation, get_body #get_sun, get_moon +from astropy.coordinates import ICRS, Galactic, FK4, CIRS, AltAz, LSR from astropy.utils.iers.iers import conf from astropy.table import Table from astropy.time import Time @@ -60,6 +60,7 @@ def __init__( sky_coords_ra = np.zeros(len(table)) sky_coords_dec = np.zeros(len(table)) + for index, row in enumerate(table): coordinate_system = row["coordinate_system"] coordinate_a = row["coordinate_a"] @@ -78,19 +79,27 @@ def __init__( sky_coords_dec[index] = sky_coord_transformed.dec.degree self.sky_coord_names[name] = index - self.sky_coords = SkyCoord( - ra=sky_coords_ra * u.deg, dec=sky_coords_dec * u.deg, frame=CIRS - ) self.location = EarthLocation.from_geodetic( lat=observer_lat * u.deg, lon=observer_lon * u.deg, height=observer_elevation * u.m, ) + + self.sky_coords = SkyCoord( + ra=sky_coords_ra * u.deg, dec=sky_coords_dec * u.deg, frame=CIRS, location=self.location) + + self.bodies = ["Sun", "Moon", "Jupiter"] #list bodies we want to track so other functions can grab these (really should pull in from config file) + ##variable to hold a target skycoord object + + self.target = SkyCoord(ra= 0*u.deg, dec= 0*u.deg,frame='icrs', location=self.location) + + self.latest_time = None self.refresh_time = refresh_time * u.second self.az_el_dict = {} - self.vlsr_dict = {} + self.target_coords =(0,0) #separate thing just to store target to so we don't display the point in the gui + # self.vlsr_dict = {} # self.time_interval_dict = {} self.time_interval_dict = self.inital_azeltime() @@ -99,6 +108,7 @@ def __init__( # self.update_azeltime() conf.auto_download = auto_download + def calculate_az_el(self, name, time, alt_az_frame): """Calculates Azimuth and Elevation of the Specified Object at the Specified Time @@ -116,77 +126,48 @@ def calculate_az_el(self, name, time, alt_az_frame): (float, float) (az, el) Tuple """ - if name == "Sun": - alt_az = get_sun(time).transform_to(alt_az_frame) - elif name == "Moon": - alt_az = get_moon(time, self.location).transform_to(alt_az_frame) + if (name == "Sun") or (name =="Moon"): + alt_az = get_body(time=time, body=name,location=self.location).transform_to(alt_az_frame) else: alt_az = self.sky_coords[self.sky_coord_names[name]].transform_to( alt_az_frame ) return alt_az.az.degree, alt_az.alt.degree - def calculate_vlsr(self, name, time, frame): - """Calculates the velocity in the local standard of rest. - Parameters - ---------- - name : str - Name of the Object being Tracked - time : Time - Current Time (only necessary for Sun/Moon Ephemeris) - alt_az_frame : AltAz - AltAz Frame Object + def calculate_vlsr(self, sky_coord, time): - Returns - ------- - float - vlsr in km/s. """ - if name == "Sun": - tframe = get_sun(time).transform_to(frame) - vlsr = tframe.radial_velocity_correction(obstime=time) - elif name == "Moon": - tframe = get_moon(time).transform_to(frame) - vlsr = tframe.radial_velocity_correction(obstime=time) - else: - tframe = self.sky_coord_names[name].transform_to(frame) - vlsr = tframe.radial_velocity_correction(obstime=time) - - return vlsr.to(u.km / u.s).value - - def calculate_vlsr_azel(self, az_el, time=None): - """Takes an AzEl tuple and derives the vlsr from Location + Calculates observer velocity correction in the local standard of rest + (e.g. relative to galactic center) along telescope line of sight. + Note we do not actuially care about the details of the object and are not attempting to + correct for target motion. We only care about where we are pointing. Parameters ---------- - az_el : (float, float) - Azimuth and Elevation - time : AstroPy Time Obj - Time of Conversion - - Returns - ------- - float - vlsr in km/s. + sky_coord : SkyCoord + Sky cordinate object + note: must contain location attribute + time : Time + Time for conversion """ - - if time is None: - time = Time.now() - - az, el = az_el - start_frame = AltAz( - obstime=time, location=self.location, alt=el * u.deg, az=az * u.deg - ) - end_frame = Galactic() - result = start_frame.transform_to(end_frame) - sk1 = SkyCoord(result) - f1 = AltAz(obstime=time, location=self.location) - vlsr = sk1.transform_to(f1).radial_velocity_correction(obstime=time) - - return vlsr.to(u.km/u.s).value - + + #cast anything into icrs to make life easy (note this means AltAz frames must contain a time) + sky_coord_radec = sky_coord.transform_to('icrs') + + #barycentric correction + v_bary = sky_coord_radec.radial_velocity_correction(obstime=time) + + sky_coord_no_vel = ICRS(ra=sky_coord_radec.ra.deg*u.deg, dec=sky_coord_radec.dec.deg*u.deg, + pm_ra_cosdec=0*u.mas/u.yr, pm_dec=0*u.mas/u.yr, + radial_velocity=0*u.km/u.yr, distance = 10*u.pc) + #solar motion wrt galactic center + v_sun = sky_coord_no_vel.transform_to(LSR()).radial_velocity + + return (v_bary+v_sun).to(u.km/u.s).value + + def convert_to_gal_coord(self, az_el, time=None): """Converts an AzEl Tuple into a Galactic Tuple from Location @@ -221,26 +202,31 @@ def update_all_az_el(self): ------- None """ - if ( - self.latest_time is not None - and Time.now() < self.latest_time + self.refresh_time - ): + if (self.latest_time is not None + and Time.now() < self.latest_time + self.refresh_time): return + time = Time.now() frame = AltAz(obstime=time, location=self.location) transformed = self.sky_coords.transform_to(frame) + for name in self.sky_coord_names: index = self.sky_coord_names[name] self.az_el_dict[name] = ( transformed.az[index].degree, transformed.alt[index].degree, ) - vlsr = transformed[index].radial_velocity_correction(obstime=time) - self.vlsr_dict[name] = vlsr.to(u.km / u.s).value - self.az_el_dict["Sun"] = self.calculate_az_el("Sun", time, frame) - self.vlsr_dict["Sun"] = self.calculate_vlsr("Sun", time, frame) - self.az_el_dict["Moon"] = self.calculate_az_el("Moon", time, frame) - self.vlsr_dict["Moon"] = self.calculate_vlsr("Moon", time, frame) + #vlsr = self.calculate_vlsr(transformed[index], time) + #self.vlsr_dict[name] = vlsr + + #deal with annoying things that move against the sky + + for body in self.bodies: + body_coords = get_body(time=time, body=body,location=self.location).transform_to(frame) + self.az_el_dict[body] = (body_coords.az.degree, body_coords.alt.degree) + #self.vlsr_dict[body] = self.calculate_vlsr(body_coords, time) + + #and prepredict things (do we actually need this?) for time_passed in range(0, 61, 5): @@ -254,13 +240,45 @@ def update_all_az_el(self): transformed.az[index].degree, transformed.alt[index].degree, ) - self.time_interval_dict[time_passed]["Sun"] = self.calculate_az_el( - "Sun", time, frame) - self.time_interval_dict[time_passed]["Moon"] = self.calculate_az_el( - "Moon", time, frame) + + for body in self.bodies: + body_coords = get_body(time=time, body=body,location=self.location).transform_to(frame) + self.time_interval_dict[time_passed][body] = (body_coords.az.degree, body_coords.alt.degree) self.latest_time = time + def update_track(self, object_id): + + time = Time.now() + frame = AltAz(obstime=time, location=self.location) + + if object_id == "target": + transformed = self.target.transform_to(frame) + self.target_coords = ( + transformed.az.degree, + transformed.alt.degree + ) + + elif object_id in self.sky_coord_names: #then this is a normal programmed object and we'll just rerun the usual calculation only for that point + index = self.sky_coord_names[object_id] + transformed = self.sky_coords[index].transform_to(frame) + self.target_coords = ( + transformed.az.degree, + transformed.alt.degree + ) + + elif object_id in self.bodies: #annoying things like planets + body_coords = get_body(time=time, body=object_id,location=self.location).transform_to(frame) + self.target_coords = (body_coords.az.degree, body_coords.alt.degree) + + else: + return + + def get_track_azimuth_elevation(self,object_id): #for tracking updates + + return self.target_coords + + def get_all_azimuth_elevation(self): """Returns Dictionary Mapping the Objects to their Current AzEl Coordinates @@ -280,39 +298,39 @@ def get_all_azel_time(self): # return return self.time_interval_dict - def get_azimuth_elevation(self, name, time_offset): - """Returns Individual Object AzEl at Specified Time Offset - - Parameters - ---------- - name : str - Object Name - time_offset : Time - Any Offset from the Current Time - Returns - ------- - (float, float) - (az, el) Tuple - """ - if time_offset == 0: - return self.get_all_azimuth_elevation()[name] - else: - time = Time.now() + time_offset - return self.calculate_az_el( - name, time, AltAz(obstime=time, location=self.location) - ) - - def get_all_vlsr(self): - return self.vlsr_dict - - def get_vlsr(self, name, time_offset=0): - - if time_offset == 0: - return self.get_all_vlsr()[name] - else: - time = Time.now() + time_offset - frame = AltAz(obstime=time, location=self.location) - return self.calculate_vlsr(name, time, frame) + # def get_azimuth_elevation(self, name, time_offset): + # """Returns Individual Object AzEl at Specified Time Offset + + # Parameters + # ---------- + # name : str + # Object Name + # time_offset : Time + # Any Offset from the Current Time + # Returns + # ------- + # (float, float) + # (az, el) Tuple + # """ + # if time_offset == 0: + # return self.get_all_azimuth_elevation()[name] + # else: + # time = Time.now() + time_offset + # return self.calculate_az_el( + # name, time, AltAz(obstime=time, location=self.location) + # ) + + #def get_all_vlsr(self): + # return self.vlsr_dict + + # def get_vlsr(self, name, time_offset=0): #not used + + # if time_offset == 0: + # return self.get_all_vlsr()[name] + # else: + # time = Time.now() + time_offset + # frame = AltAz(obstime=time, location=self.location) + # return self.calculate_object_vlsr(name, time, frame) def inital_azeltime(self): new_dict = {} diff --git a/srt/dashboard/app.py b/srt/dashboard/app.py index eaaa0ee6..d93dde1d 100644 --- a/srt/dashboard/app.py +++ b/srt/dashboard/app.py @@ -88,7 +88,9 @@ def generate_app(config_dir, config_dict): pio.templates.default = "seaborn" # Style Choice for Graphs curfold = Path(__file__).parent.absolute() # Generate Sidebar Objects - side_title = software + + side_title = "Medium Radio Telescope" + image_filename = curfold.joinpath( "images", "MIT_HO_logo_landscape.png" ) # replace with your own image @@ -272,7 +274,7 @@ def update_status_display(n): az_offset = el_offset = np.nan cf = np.nan bandwidth = np.nan - status_string = "SRT Not Connected" + status_string = "MRT Not Connected" vlsr = np.nan else: lat = status["location"]["latitude"] @@ -286,11 +288,11 @@ def update_status_display(n): vlsr = status["vlsr"] time_dif = time() - status["time"] if time_dif > 5: - status_string = "SRT Daemon Not Available" + status_string = "MRT Daemon Not Available" elif status["queue_size"] == 0 and status["queued_item"] == "None": - status_string = "SRT Inactive" + status_string = "MRT Inactive" else: - status_string = "SRT In Use!" + status_string = "MRT In Use!" if config_dict["SOFTWARE"] == "Very Small Radio Telescope": status_string = f""" @@ -304,12 +306,12 @@ def update_status_display(n): else: status_string = f""" #### {status_string} - - Location Lat, Long: {lat:.1f}, {lon:.1f} deg - - Motor Az, El: {az:.1f}, {el:.1f} deg - - Motor Offsets: {az_offset:.1f}, {el_offset:.1f} deg + - Location Lat, Long: {lat:.2f}, {lon:.2f} deg + - Motor Az, El: {az:.2f}, {el:.2f} deg + - Motor Offsets: {az_offset:.2f}, {el_offset:.2f} deg - Center Frequency: {cf / pow(10, 6)} MHz - Bandwidth: {bandwidth / pow(10, 6)} MHz - - VLSR: {vlsr:.1f} km/s + - VLSR: {vlsr:.2f} km/s """ return status_string diff --git a/srt/dashboard/layouts/graphs.py b/srt/dashboard/layouts/graphs.py index 981f549e..07ef4846 100644 --- a/srt/dashboard/layouts/graphs.py +++ b/srt/dashboard/layouts/graphs.py @@ -67,31 +67,24 @@ def generate_az_el_graph( ) ) - # Marker for visability, basicaslly beamwidth with azimuth stretched out for high elevation angles. + # Marker for visibility, basicaslly beamwidth with azimuth stretched out for high elevation angles. az_l = current_location[0] el_l = current_location[1] - el_u = el_l + .5*beam_width - el_d = el_l - .5*beam_width - - azu = .5*beam_width/np.cos(el_u * np.pi / 180.0) - azd = .5*beam_width/np.cos(el_d * np.pi / 180.0) - x_vec = [max(az_l-azd, 0), min(az_l-azu, 360), - max(az_l+azu, 0), min(az_l+azd, 360), max(az_l-azd, 0)] - y_vec = [max(el_d, 0), min(el_u, 90), min( - el_u, 90), min(el_d, 90), max(el_d, 0)] + az_bw = beam_width/np.cos(el_l * np.pi / 180.0) - fig.add_trace( - go.Scatter( - x=x_vec, - y=y_vec, - fill="toself", - fillcolor="rgba(147,112,219,0.1)", - text=["Visability"], - name='Visability', - mode="markers", - marker_color=["rgba(147,112,219, .8)" for _ in x_vec] - ) + fig.add_shape( + type="circle", + xref="x", + yref="y", + x0=az_l-az_bw/2, + y0=el_l-beam_width/2, + x1=az_l+az_bw/2, + y1=el_l+beam_width/2, + fillcolor="grey", + layer="below", + #label=dict(text="Beamwidth", textposition="top center", + # font=dict(color="White")) ) fig.add_trace( @@ -276,8 +269,12 @@ def generate_zoom_graph( """ fig = go.Figure() - az_lower_display_lim = current_location[0]-beam_width*2 - az_upper_display_lim = current_location[0]+beam_width*2 + #correct for azel coordinates distortion on sky + #el_bw = beam_width + az_bw = beam_width/np.cos(current_location[1] * np.pi / 180.0) + + az_lower_display_lim = current_location[0]-az_bw*2 + az_upper_display_lim = current_location[0]+az_bw*2 el_lower_display_lim = current_location[1]-beam_width*2 el_upper_display_lim = current_location[1]+beam_width*2 @@ -295,13 +292,13 @@ def generate_zoom_graph( ) ) fig.add_shape( - type="rect", + type="circle", xref="x", yref="y", - x0=current_location[0]-beam_width, - y0=current_location[1]-beam_width, - x1=current_location[0]+beam_width, - y1=current_location[1]+beam_width, + x0=current_location[0]-az_bw/2, + y0=current_location[1]-beam_width/2, + x1=current_location[0]+az_bw/2, + y1=current_location[1]+beam_width/2, fillcolor="lightgrey", layer="below", label=dict(text="Beamwidth", textposition="top center", @@ -714,8 +711,76 @@ def emptygraph(xlabel, ylabel, title): return fig +def generate_npoint_raw(az_in, el_in, d_az, d_el, pow_in, cent, sides): + """Creates the n-point graph image with raw data without interpolation + + Parameters + ---------- + az_in : array_like + List of azimuth locations. + el_in : array_like + List of elevation locations. + d_az : float + Resolution of power measurements in the azimuth direction. + d_el : float + REsolution of power measurements in elevation direction. + pow_in : array_like + List of power measurements for the given locations of the antenna. + cent : array_like + Center point of the object being imaged. + sides : list + Number of pointers per side. + + Returns + ------- + fig : plotly.fig + Figure object. + """ + + # create the output grid + az_in = np.array(az_in) + el_in = np.array(el_in) + + idx_center = int(np.ceil(len(az_in)/2)) + az_center = az_in[idx_center] + el_center = el_in[idx_center] + + az_range = np.linspace(az_center-d_az, az_center+d_az, sides[0]) + el_range = np.linspace(el_center-d_el, el_center+d_el, sides[1]) + + pow_in = np.array(pow_in) + pow_grid = np.reshape(pow_in, (sides[0],sides[1])) + # Make the contour plot + d1 = go.Contour(z=pow_grid, x=az_range, y=el_range, colorscale="Viridis") + fig = go.Figure( + data=d1, + layout={ + "title": "Raw N-Point Scan", + "xaxis_title": "Azimuth Angle", + "yaxis_title": "Elevation Angle", + "uirevision": True, + }, + ) + #fig.add_annotation( + # x=xaout[10], + # y=xaout[20], + # xanchor="left", + # text=antext0, + # showarrow=False, + # font=dict(family="Courier New, monospace", size=13, color="#ffffff"), + #) + + #fig.add_annotation( + # x=xaout[10], + # y=xaout[10], + # text=antext1, + # xanchor="left", + # showarrow=False, + # font=dict(family="Courier New, monospace", size=13, color="#ffffff"), + #) + return fig -def generate_npoint(az_in, el_in, d_az, d_el, pow_in, cent, sides): +def generate_npoint_interpolated(az_in, el_in, d_az, d_el, pow_in, cent, sides): """Creates the n-point graph image. Parameters @@ -773,7 +838,7 @@ def generate_npoint(az_in, el_in, d_az, d_el, pow_in, cent, sides): fig = go.Figure( data=d1, layout={ - "title": "N-Point Scan", + "title": "N Point Sinc Interpolated", "xaxis_title": "Normalized x", "yaxis_title": "Normalized y", "uirevision": True, diff --git a/srt/dashboard/layouts/monitor_page.py b/srt/dashboard/layouts/monitor_page.py index ecc5ccf3..c81e525e 100644 --- a/srt/dashboard/layouts/monitor_page.py +++ b/srt/dashboard/layouts/monitor_page.py @@ -36,7 +36,7 @@ generate_power_history_graph, generate_spectrum_graph, generate_zoom_graph, - generate_npoint, + generate_npoint_raw, emptygraph, ) @@ -44,15 +44,20 @@ from srt import config_loader -root_folder = Path(__file__).parent.parent.parent.parent +#root_folder = Path(__file__).parent.parent.parent.parent +#the above appears to be broken +root_folder = "$HOME/srt-py" - -def get_all_objects(config_file="config/sky_coords.csv",): - table = Table.read(Path(root_folder, config_file), format="ascii.csv") +def get_all_objects(coords_file="config/sky_coords.csv",): all_objects = ["Sun", "Moon"] - for index, row in enumerate(table): - name = row["name"] - all_objects.append(name) + try: + table = Table.read(Path(root_folder, coords_file), format="ascii.csv") + + for index, row in enumerate(table): + name = row["name"] + all_objects.append(name) + except: + table = all_objects #just to do something return all_objects @@ -89,6 +94,7 @@ def generate_first_row(): ) + def generate_srt_azel(): """Generates AzEl Display @@ -111,36 +117,6 @@ def generate_srt_azel(): ), ) -def generate_npointlayout(): - """Generates N Point Display - - Returns - ------- - Div: html.Div - containing n point graph if srt - """ - return html.Div( - [ - html.Div( - [ - dcc.Store(id="npoint_info", storage_type="session"), - html.Div( - [dcc.Graph(id="npoint-graph")], - className="pretty_container six columns", - ), - # html.Div( - # [dcc.Graph(id="beamsswitch-graph")], - # className="pretty_container six columns", - # ), - ], - className="flex-display", - style={ - "justify-content": "left", - "margin": "5px", - }, - ), - ] - ) def generate_srt_second_row(): """Generates N Point Display and zoomed in map @@ -156,7 +132,7 @@ def generate_srt_second_row(): [ dcc.Store(id="npoint_info", storage_type="session"), html.Div( - [dcc.Graph(id="npoint-graph")], + [dcc.Graph(id="npoint-graph-1")], className="pretty_container six columns", ), html.Div( @@ -199,6 +175,41 @@ def generate_second_row(): ), ) +def generate_npointlayout(): + """Generates N Point Display + + Returns + ------- + Div containing n point graph if srt + """ + return html.Div( + [ + html.Div( + [ + dcc.Store(id="npoint_info", storage_type="session"), + html.Div( + [dcc.Graph(id="npoint-graph-1")], + className="pretty_container six columns", + ), + #html.Div( + # [dcc.Graph(id="npoint-graph-2")], + # className="pretty_container six columns", + # ), + # html.Div( + # [dcc.Graph(id="beamsswitch-graph")], + # className="pretty_container six columns", + # ), + ], + className="flex-display", + style={ + "justify-content": "left", + "margin": "5px", + }, + ), + ] + ) + + def generate_third_row(): """Generates Third Row (AzEl Time) Display @@ -488,23 +499,40 @@ def generate_popups(software): dbc.Modal( [ - dbc.ModalHeader("Enter Azimuth and Elevation"), + dbc.ModalHeader("Enter Pointing Coordinates"), dbc.ModalBody( [ dcc.Input( - id="azimuth", + id="val1", type="number", debounce=True, - placeholder="Azimuth", + placeholder="Az/RA/l", ), dcc.Input( - id="elevation", + id="val2", type="number", debounce=True, - placeholder="Elevation", + placeholder="El/Dec/b", + ), + dcc.RadioItems( + options=[ + {"label": "Az/El", + "value": "AzEl"}, + { + "label": "Ra/Dec", + "value": "RaDec", + }, + { + "label": "Galactic", + "value": "Galactic", + }, + ], + id="coord-options", + value="", ), ] ), + dbc.ModalFooter( [ dbc.Button( @@ -526,6 +554,41 @@ def generate_popups(software): ], id="point-modal", ), + dbc.Modal( + [ + dbc.ModalHeader("Enter the New N-point Grid Size"), + dbc.ModalBody( + [ + dcc.Input( + id="npoint-size", + type="number", + debounce=True, + placeholder="Grid Edge Points sqrt(N)", + style={"width": "100%"}, + ), + ] + ), + dbc.ModalFooter( + [ + dbc.Button( + "Yes", + id="npoint-set-btn-yes", + className="ml-auto", + # block=True, + color="primary", + ), + dbc.Button( + "No", + id="npoint-set-btn-no", + className="ml-auto", + # block=True, + color="secondary", + ), + ] + ), + ], + id="n-point-modal", + ), dbc.Modal( [ dbc.ModalHeader("Enter the New Center Frequency"), @@ -596,6 +659,41 @@ def generate_popups(software): ], id="samp-modal", ), + dbc.Modal( + [ + dbc.ModalHeader("Enter the New RF Gain"), + dbc.ModalBody( + [ + dcc.Input( + id="rf_gain", + type="number", + debounce=True, + placeholder="RF Gain (dB)", + style={"width": "100%"}, + ), + ] + ), + dbc.ModalFooter( + [ + dbc.Button( + "Yes", + id="gain-btn-yes", + className="ml-auto", + # block=True, + color="primary", + ), + dbc.Button( + "No", + id="gain-btn-no", + className="ml-auto", + # block=True, + color="secondary", + ), + ] + ), + ], + id="gain-modal", + ), dbc.Modal( [ dbc.ModalHeader("Enter the Motor Offsets"), @@ -635,7 +733,9 @@ def generate_popups(software): ), ], id="offset-modal", + ), + dbc.Modal( [ dbc.ModalHeader("Start Recording"), @@ -799,17 +899,24 @@ def generate_layout(software): # ], "Antenna": [ dbc.DropdownMenuItem("Stow", id="btn-stow"), - dbc.DropdownMenuItem("Set AzEl", id="btn-point-azel"), + #dbc.DropdownMenuItem("Set AzEl", id="btn-point-azel"), + dbc.DropdownMenuItem("Set Coordinates", id="btn-point-coords"), dbc.DropdownMenuItem("Set Offsets", id="btn-set-offset"), + dbc.DropdownMenuItem("Set N-point size", id="btn-set-npoint"), ], "Radio": [ dbc.DropdownMenuItem("Set Frequency", id="btn-set-freq"), dbc.DropdownMenuItem("Set Bandwidth", id="btn-set-samp"), + dbc.DropdownMenuItem("Set RF Gain", id="btn-set-gain"), + ], + "Calibration": [ + dbc.DropdownMenuItem("Calibrate", id="btn-calibrate"), + dbc.DropdownMenuItem("Noise Reference on", id="btn-calon"), + dbc.DropdownMenuItem("Noise Reference off", id="btn-caloff"), ], "Routine": [ dbc.DropdownMenuItem("Start Recording", id="btn-start-record"), dbc.DropdownMenuItem("Stop Recording", id="btn-stop-record"), - dbc.DropdownMenuItem("Calibrate", id="btn-calibrate"), dbc.DropdownMenuItem("Upload CMD File", id="btn-cmd-file"), ], "Power": [ @@ -839,7 +946,7 @@ def generate_layout(software): generate_first_row(), generate_srt_azel(), generate_srt_second_row(), - generate_third_row(), + #generate_third_row(), generate_popups(software), html.Div(id="signal", style={"display": "none"}), ] @@ -967,11 +1074,11 @@ def npointstore(n, npdata): raise PreventUpdate @app.callback( - Output("npoint-graph", "figure"), + Output("npoint-graph-1", "figure"), [Input("npoint_info", "modified_timestamp")], [State("npoint_info", "data")], ) - def update_n_point(ts, npdata): + def update_n_point_raw(ts, npdata): """Update the npoint track info Parameters @@ -1004,8 +1111,50 @@ def update_n_point(ts, npdata): sc = npdata["scan_center"] plist = npdata["pwr"] sd = npdata["sides"] - ofig = generate_npoint(az_a, el_a, mdiff[0], mdiff[1], plist, sc, sd) + ofig = generate_npoint_raw(az_a, el_a, mdiff[0], mdiff[1], plist, sc, sd) return ofig + + # @app.callback( + # Output("npoint-graph-2", "figure"), + # [Input("npoint_info", "modified_timestamp")], + # [State("npoint_info", "data")], + # ) + # def update_n_point_interpolated(ts, npdata): + # """Update the npoint track info + + # Parameters + # ---------- + # ts : int + # modified time stamp + # npdata : dict + # will hold N- point data. + + # Returns + # ------- + # ofig : plotly.fig + # Plotly figure + # """ + + # if ts is None: + # raise PreventUpdate + # if npdata is None: + # return emptygraph("x", "y", "N-Point Scan") + + # if npdata.get("scan_center", [1, 1])[0] == 0: + # return emptygraph("x", "y", "N-Point Scan") + + # az_a = [] + # el_a = [] + # for irot in npdata["rotor_loc"]: + # az_a.append(irot[0]) + # el_a.append(irot[1]) + # mdiff = npdata["maxdiff"] + # sc = npdata["scan_center"] + # plist = npdata["pwr"] + # sd = npdata["sides"] + # ofig = generate_npoint_interpolated(az_a, el_a, mdiff[0], mdiff[1], plist, sc, sd) + # return ofig + @app.callback( Output("start-warning", "children"), @@ -1280,28 +1429,58 @@ def coords_click_func(n_clicks_btn, n_clicks_yes, n_clicks_no, is_open, lat, lon if n_clicks_yes or n_clicks_no or n_clicks_btn: return not is_open return is_open - + @ app.callback( Output("point-modal", "is_open"), [ - Input("btn-point-azel", "n_clicks"), + Input("btn-point-coords", "n_clicks"), Input("point-btn-yes", "n_clicks"), Input("point-btn-no", "n_clicks"), ], [ State("point-modal", "is_open"), - State("azimuth", "value"), - State("elevation", "value"), + State("val1", "value"), + State("val2", "value"), + State("coord-options", "value"), ], ) - def point_click_func(n_clicks_btn, n_clicks_yes, n_clicks_no, is_open, az, el): + def point_click_func(n_clicks_btn, n_clicks_yes, n_clicks_no, is_open, val1, val2, coord_option): ctx = dash.callback_context if not ctx.triggered: return is_open else: button_id = ctx.triggered[0]["prop_id"].split(".")[0] if button_id == "point-btn-yes": - command_thread.add_to_queue(f"azel {az} {el}") + if coord_option == "RaDec": + command_thread.add_to_queue(f"radec {val1} {val2}") + elif coord_option == "Galactic": + command_thread.add_to_queue(f"galactic {val1} {val2}") + else: #coord_option == "AzEl": + command_thread.add_to_queue(f"azel {val1} {val2}") + if n_clicks_yes or n_clicks_no or n_clicks_btn: + return not is_open + return is_open + + @app.callback( + Output("n-point-modal", "is_open"), + [ + Input("btn-set-npoint", "n_clicks"), + Input("npoint-set-btn-yes", "n_clicks"), + Input("npoint-set-btn-no", "n_clicks"), + ], + [ + State("n-point-modal", "is_open"), + State("npoint-size", "value"), + ], + ) + def freq_click_func(n_clicks_btn, n_clicks_yes, n_clicks_no, is_open, npoints): + ctx = dash.callback_context + if not ctx.triggered: + return is_open + else: + button_id = ctx.triggered[0]["prop_id"].split(".")[0] + if button_id == "npoint-set-btn-yes": + command_thread.add_to_queue(f"npointset {npoints}") if n_clicks_yes or n_clicks_no or n_clicks_btn: return not is_open return is_open @@ -1354,6 +1533,30 @@ def samp_click_func(n_clicks_btn, n_clicks_yes, n_clicks_no, is_open, samp): return not is_open return is_open + @app.callback( + Output("gain-modal", "is_open"), + [ + Input("btn-set-gain", "n_clicks"), + Input("gain-btn-yes", "n_clicks"), + Input("gain-btn-no", "n_clicks"), + ], + [ + State("gain-modal", "is_open"), + State("rf_gain", "value"), + ], + ) + def gain_click_func(n_clicks_btn, n_clicks_yes, n_clicks_no, is_open, gain): + ctx = dash.callback_context + if not ctx.triggered: + return is_open + else: + button_id = ctx.triggered[0]["prop_id"].split(".")[0] + if button_id == "gain-btn-yes": + command_thread.add_to_queue(f"rf_gain {gain}") + if n_clicks_yes or n_clicks_no or n_clicks_btn: + return not is_open + return is_open + @ app.callback( Output("offset-modal", "is_open"), [ @@ -1386,8 +1589,11 @@ def offset_click_func(n_clicks_btn, n_clicks_yes, n_clicks_no, is_open, az, el): Input("record-btn-yes", "n_clicks"), Input("record-btn-no", "n_clicks"), ], - [State("record-modal", "is_open"), State("record-options", - "value"), State("recording-alert", "is_open")], + [ + State("record-modal", "is_open"), + State("record-options","value"), + State("recording-alert", "is_open") + ], ) def record_click_func( n_clicks_btn, n_clicks_yes, n_clicks_no, is_open, record_option, is_open_alert @@ -1399,17 +1605,19 @@ def record_click_func( button_id = ctx.triggered[0]["prop_id"].split(".")[0] if button_id == "record-btn-yes": command_thread.add_to_queue(f"record {record_option}") - print("alert") + #print("alert") if n_clicks_yes or n_clicks_no or n_clicks_btn: - print("open") + #print("open") return not is_open return is_open @ app.callback( Output("recording-alert", "is_open"), - [Input("record-btn-yes", "n_clicks"), - Input("btn-stop-record", "n_clicks")], + [ + Input("record-btn-yes", "n_clicks"), + Input("btn-stop-record", "n_clicks") + ], [], ) def record_alert_func(n_clicks_start, n_clicks_stop): @@ -1489,19 +1697,25 @@ def run_srt_daemon(configuration_dir, configuration_dict): @ app.callback( Output("signal", "children"), [ - # Input("btn-stow", "n_clicks"), + Input("btn-stow", "n_clicks"), Input("btn-stop-record", "n_clicks"), Input("btn-quit", "n_clicks"), Input("btn-calibrate", "n_clicks"), + Input("btn-calon", "n_clicks"), + Input("btn-caloff", "n_clicks"), ], - [State("recording-alert", "is_open")] + [ + State("recording-alert", "is_open") + ] ) def cmd_button_pressed( - # n_clicks_stow, + n_clicks_stow, n_clicks_stop_record, n_clicks_shutdown, n_clicks_calibrate, - is_open + n_clicks_calon, + n_clicks_caloff, + is_open, ): ctx = dash.callback_context if not ctx.triggered: @@ -1517,3 +1731,7 @@ def cmd_button_pressed( command_thread.add_to_queue("quit") elif button_id == "btn-calibrate": command_thread.add_to_queue("calibrate") + elif button_id == "btn-calon": + command_thread.add_to_queue("calon") + elif button_id == "btn-caloff": + command_thread.add_to_queue("caloff") diff --git a/srt/dashboard/messaging/raw_radio_fetcher.py b/srt/dashboard/messaging/raw_radio_fetcher.py index 5fd43640..3883d071 100644 --- a/srt/dashboard/messaging/raw_radio_fetcher.py +++ b/srt/dashboard/messaging/raw_radio_fetcher.py @@ -51,7 +51,7 @@ def run(self): socket.subscribe("") while True: rec = socket.recv() - print(len(rec)) + #print(len(rec)) var = np.frombuffer(rec, dtype="complex64") new_history_index = self.history_index + len(var) self.history.put(