| | 1 | == Measurement tools == |
| | 2 | This tutorial will demonstrate data collection method using the OML interface. |
| | 3 | |
| | 4 | == Prerequisites == |
| | 5 | 1. Accounts, ssh keys |
| | 6 | 2. Reservation |
| | 7 | 3. Familiarity with USRPs. |
| | 8 | |
| | 9 | === Description === |
| | 10 | This tutorial utilizes an existing application provided by the UHD driver. The application used is ''rx_multi_sample'' which is a c++ application that configures and reads samples from multiple the USRPs. A c++ based OML-wrapper has been added to this application to store FFTed blocks of samples to an OML database server. |
| | 11 | |
| | 12 | === Set up === |
| | 13 | 1. First ssh into the console that you made a reservation. |
| | 14 | 2. Use the ''omf stat'' to find all the experiment servers and SDRs that are seen by OMF. |
| | 15 | {{{ |
| | 16 | console> omf stat -t all |
| | 17 | INFO NodeHandler: OMF Experiment Controller 5.4 (git 861d645) |
| | 18 | INFO NodeHandler: Slice ID: default_slice (default) |
| | 19 | INFO NodeHandler: Experiment ID: default_slice-2019-05-14t10.58.59.167-04.00 |
| | 20 | INFO NodeHandler: Message authentication is disabled |
| | 21 | INFO property.resetDelay: resetDelay = 200 (Fixnum) |
| | 22 | INFO property.resetTries: resetTries = 1 (Fixnum) |
| | 23 | INFO property.nodes: nodes = "system:topo:all" (String) |
| | 24 | INFO property.summary: summary = false (FalseClass) |
| | 25 | INFO Topology: Loaded topology 'system:topo:all'. |
| | 26 | |
| | 27 | Talking to the CMC service, please wait |
| | 28 | ----------------------------------------------- |
| | 29 | Node: mob1.sb1.cosmos-lab.org State: POWERON |
| | 30 | Node: mob2.sb1.cosmos-lab.org State: POWERON |
| | 31 | Node: mob3.sb1.cosmos-lab.org State: POWERON |
| | 32 | Node: sdr1-lg1.sb1.cosmos-lab.org State: POWERON |
| | 33 | Node: sdr1-md1.sb1.cosmos-lab.org State: POWERON |
| | 34 | Node: sdr2-lg1.sb1.cosmos-lab.org State: POWERON |
| | 35 | Node: sdr2-md1.sb1.cosmos-lab.org State: POWERON |
| | 36 | Node: srv1-lg1.sb1.cosmos-lab.org State: POWERON |
| | 37 | Node: srv2-lg1.sb1.cosmos-lab.org State: POWERON |
| | 38 | ----------------------------------------------- |
| | 39 | |
| | 40 | INFO EXPERIMENT_DONE: Event triggered. Starting the associated tasks. |
| | 41 | INFO NodeHandler: |
| | 42 | INFO NodeHandler: Shutting down experiment, please wait... |
| | 43 | INFO NodeHandler: |
| | 44 | INFO run: Experiment default_slice-2019-05-14t10.58.59.167-04.00 finished after 0:5 |
| | 45 | }}} |
| | 46 | |
| | 47 | 3. Load the ''baseline-sdr.ndz'' image onto the experiment server. This image has the UHD driver and other pertinent libraries pre-installed. For this tutoral we'll use ''srv1-lg1.sb1.cosmos-lab.org'' as the experiment server. |
| | 48 | {{{ |
| | 49 | console> omf load -i baseline-sdr.ndz -t srv1-lg1.sb1.cosmos-lab.org |
| | 50 | }}} |
| | 51 | |
| | 52 | 4. After loading is complete turn on the experiment server. We'll also use the ''sdr1-lg1.sb1.cosmos-lab.org'' which is an N310 USRP. So turn this on as well. |
| | 53 | {{{ |
| | 54 | console> omf tell -a on -t srv1-lg1.sb1.cosmos-lab.org,sdr1-lg1.sb1.cosmos-lab.org |
| | 55 | }}} |
| | 56 | |
| | 57 | 5. Use the ''omf stat'' command again to verify ON/OFF status of the devices. |
| | 58 | {{{ |
| | 59 | console> omf stat -t srv1-lg1.sb1.cosmos-lab.org,sdr1-lg1.sb1.cosmos-lab.org |
| | 60 | }}} |
| | 61 | |
| | 62 | |
| | 63 | 7. Configure interface |
| | 64 | {{{ |
| | 65 | ifconfig enp1s0 10.115.1.1 netmask 255.255.0.0 mtu 8000 |
| | 66 | }}} |
| | 67 | |
| | 68 | 6. Next ssh into the experiment server and discover the USRP that we turned on. We'll need the ip address for this USRP; At the command line do a 'host sdr1-lg1.sb1.cosmos-lab.org' to find its ip address. Then run ''uhd_find_devices'' with the --args option to discovery the USRP. |
| | 69 | {{{ |
| | 70 | native@localhost:~$ host sdr1-lg1.sb1.cosmos-lab.org |
| | 71 | sdr1-lg1.sb1.cosmos-lab.org has address 10.113.2.1 |
| | 72 | |
| | 73 | native@localhost:~$ uhd_find_devices --args="addr=10.113.2.1" |
| | 74 | |
| | 75 | <<some output here>>> |
| | 76 | }}} |
| | 77 | |
| | 78 | === Explain ''rx_multi_receive'' application and how to use === |
| | 79 | Navigate to the ~/RX_MULTI_RECEIVE directory - this contains the files that build the ''rx_multi_receive'' application. |
| | 80 | This is a c++ application that can be run on the experiment server to collect a set of continuous samples. |
| | 81 | |
| | 82 | Use the --help option to display all the variables ''rx_multi_receive'' accepts. |
| | 83 | {{{ |
| | 84 | root@node20-1:~/RX_MULTI_RECEIVE# ./rx_multi_receive --help |
| | 85 | linux; GNU C++ version 4.8.4; Boost_105400; UHD_003.010.001.001-0-unknown |
| | 86 | |
| | 87 | UHD RX Multi Receive Allowed options: |
| | 88 | --help help message |
| | 89 | --args arg single uhd device address args |
| | 90 | --secs arg (=1.5) number of seconds in the future to receive |
| | 91 | --nsamps arg (=10000) total number of samples to receive |
| | 92 | --freq arg (=900000000) RF center frequency in Hz for all channels |
| | 93 | --rate arg (=6250000) rate of incoming samples for all channels |
| | 94 | --gain arg (=0) gain for the RF chain for all channels |
| | 95 | --ant arg rx antenna selection for all channels |
| | 96 | --prefix arg enables file output with filename prefix |
| | 97 | --addr arg udp address: 10.10.0.10 |
| | 98 | --port arg (=1337) udp port: 1337 |
| | 99 | --sync arg (=now) synchronization method: now, pps, mimo |
| | 100 | --subdev arg subdev spec (homogeneous across motherboards) |
| | 101 | --dilv specify to disable inner-loop verbose |
| | 102 | --int-n tune USRP with integer-N tuning |
| | 103 | --channels arg (=0) which channel(s) to use (specify "0", "1", |
| | 104 | "0,1", etc) |
| | 105 | --oml-id arg OML sender ID - pass the hostname here |
| | 106 | --oml-domain arg (=omlcli) file name for OML database |
| | 107 | --oml-collect arg (=oml:3003) Storage options: |
| | 108 | network: <server:port> |
| | 109 | local text file: <file:my_file_name> |
| | 110 | }}} |
| | 111 | |
| | 112 | To use ''rx_multi_receive'' standalone on an experiment server we can using the following options |
| | 113 | {{{ |
| | 114 | root@node20-1:~/RX_MULTI_RECEIVE# ~/RX_MULTI_RECEIVE/rx_multi_receive --args="addr0=10.10.23.5" --freq 2440e6 --rate 20e6 --gain 3 |
| | 115 | --nsamps 1024000 --channels "0" --dilv |
| | 116 | }}} |
| | 117 | |
| | 118 | --args="addr0=10.10.23.5" specifies the SDR's IP address. |
| | 119 | --freq 2440e6 sets the center freq |
| | 120 | --rate 20e6 sets sampling rate |
| | 121 | --gain 3 sets receive path gain |
| | 122 | --nsamps 1024000 number of samples to collect |
| | 123 | |
| | 124 | Executing the application with only the above options will only configure the USRP's radio parameters and read in the specified nu |
| | 125 | mber of samples. However the samples will be lost once the application exits. You should see output similar to the following. |
| | 126 | {{{ |
| | 127 | |
| | 128 | linux; GNU C++ version 4.8.4; Boost_105400; UHD_003.010.001.001-0-unknown |
| | 129 | |
| | 130 | |
| | 131 | Creating the usrp device with: addr0=10.10.23.5... |
| | 132 | -- X300 initialization sequence... |
| | 133 | -- Determining maximum frame size... 7972 bytes. |
| | 134 | -- Setup basic communication... |
| | 135 | -- Loading values from EEPROM... |
| | 136 | -- Setup RF frontend clocking... |
| | 137 | -- Radio 1x clock:200 |
| | 138 | |
| | 139 | : |
| | 140 | : |
| | 141 | : |
| | 142 | |
| | 143 | Using Device: Single USRP: |
| | 144 | Device: X-Series Device |
| | 145 | Mboard 0: X310 |
| | 146 | RX Channel: 0 |
| | 147 | RX DSP: 0 |
| | 148 | RX Dboard: A |
| | 149 | RX Subdev: UBX RX |
| | 150 | TX Channel: 0 |
| | 151 | TX DSP: 0 |
| | 152 | TX Dboard: A |
| | 153 | TX Subdev: UBX TX |
| | 154 | TX Channel: 1 |
| | 155 | TX DSP: 0 |
| | 156 | TX Dboard: B |
| | 157 | TX Subdev: UBX TX |
| | 158 | |
| | 159 | Number mboards = 1 |
| | 160 | Number of rx channels = 1 |
| | 161 | Setting RX Rate: 20.000000 Msps... |
| | 162 | Actual RX Rate: 20.000000 Msps... |
| | 163 | |
| | 164 | Setting RX Freq: 2440.000000 MHz... |
| | 165 | Actual RX Freq: 2440.000000 MHz... |
| | 166 | |
| | 167 | Setting RX Gain: 3.000000 dB... |
| | 168 | Actual RX Gain: 3.000000 dB... |
| | 169 | |
| | 170 | Setting device timestamp to 0... |
| | 171 | channel_nums.size() = 1 |
| | 172 | |
| | 173 | UHD Warning: |
| | 174 | For this connection, UHD recommends a send frame size of at least 8000 for best |
| | 175 | performance, but your system's MTU will only allow 7972. |
| | 176 | This will negatively impact your maximum achievable sample rate. |
| | 177 | |
| | 178 | UHD Warning: |
| | 179 | For this connection, UHD recommends a receive frame size of at least 8000 for best |
| | 180 | performance, but your system's MTU will only allow 7972. |
| | 181 | This will negatively impact your maximum achievable sample rate. |
| | 182 | |
| | 183 | Begin streaming 1024000 samples, 1.500000 seconds in the future... |
| | 184 | samps_per_buff = 1024 |
| | 185 | num_buff_ptrs = 1000 |
| | 186 | mdbp_idx = 1000 |
| | 187 | |
| | 188 | Done! |
| | 189 | |
| | 190 | }}} |
| | 191 | |
| | 192 | To save the samples for post processing, specify the following OML arguments |
| | 193 | --oml-id `hostname` this identifies the source of the data being store. |
| | 194 | --oml-domain rx_samples file name for the OML database |
| | 195 | --oml-collect oml:3003 network storage located on oml:3003 |
| | 196 | |
| | 197 | |
| | 198 | Rerun the application with these and we'll see a few additional lines of output at the end indicating a connection to the OML database server. |
| | 199 | |
| | 200 | {{{ |
| | 201 | |
| | 202 | Writing FFT blocks into rx_samples.sq3 @ oml:3003 |
| | 203 | May 13 21:36:18 INFO OML Client V2.10.1rc [Protocol V4] Copyright 2007-2013, NICTA |
| | 204 | INFO OmlNetOutStream: attempting to connect to server at tcp://oml:3003 |
| | 205 | INFO tcp:oml:3003: Waiting for buffered queue thread to drain... |
| | 206 | |
| | 207 | }}} |
| | 208 | |
| | 209 | |
| | 210 | === Instrumenting ''rx_multi_receive'' application with OML === |
| | 211 | The source file for this application is in ~/RX_MULTI_RECEIVE/main.cpp. Without going into the details off c++ and the enitire contents of the source file, we'll make note of the following blocks of code: |
| | 212 | |
| | 213 | 1. Lines xxx creates a handle to the USRP at the specified IP address. This handle is used thoughout the source to make reference to the radio. |
| | 214 | 2. Lines xxx uses the handle to configure the radio paramters passed in from the command line. |
| | 215 | 3. The read loop on lines xxx to xxx. This block reads out the complex floating samples into a buffer that is used for post processi |
| | 216 | ng samples. |
| | 217 | 4. Lines xxx to xxx are OML additions made to send the FFTed sample data to the OML server for storage. |
| | 218 | |
| | 219 | In side this block, we create an object that performs 1024pt FFTs. |
| | 220 | {{{ |
| | 221 | std::vector<std::complex<float> > time_buff( samps_per_buff ); |
| | 222 | std::vector<std::complex<float> > freq_buff( samps_per_buff ); |
| | 223 | std::vector<float> signal_mag_instant( samps_per_buff ); |
| | 224 | |
| | 225 | fftwf_plan fft_p = fftwf_plan_dft_1d( samps_per_buff, |
| | 226 | (fftwf_complex*)&time_buff.front(), |
| | 227 | (fftwf_complex*)&freq_buff.front(), |
| | 228 | FFTW_FORWARD, |
| | 229 | FFTW_ESTIMATE); |
| | 230 | }}} |
| | 231 | |
| | 232 | We also contruct an object for writing data to an OML server. Initialized the OML object with the OML options described above. |
| | 233 | {{{ |
| | 234 | // OML object for sending data |
| | 235 | CWriteOml oml; |
| | 236 | oml.init(oml_id, oml_domain, oml_collect ); |
| | 237 | }}} |
| | 238 | |
| | 239 | Create keyed-values that will be sent for storage. Here we specify a string label with an OML type. After all the keyed-value pairs have been defined, we start a connection with the OML service. |
| | 240 | {{{ |
| | 241 | std::vector< std::pair<std::string, OmlValueT> > _omlKeys; |
| | 242 | // string label OML type |
| | 243 | _omlKeys.push_back( std::make_pair("mboard_id", OML_STRING_VALUE)); |
| | 244 | _omlKeys.push_back( std::make_pair("mboard_serial", OML_STRING_VALUE)); |
| | 245 | _omlKeys.push_back( std::make_pair("mboard_name", OML_STRING_VALUE)); |
| | 246 | _omlKeys.push_back( std::make_pair("rx_id", OML_STRING_VALUE)); |
| | 247 | _omlKeys.push_back( std::make_pair("rx_subdev_name", OML_STRING_VALUE)); |
| | 248 | _omlKeys.push_back( std::make_pair("rx_subdev_spec", OML_STRING_VALUE)); |
| | 249 | _omlKeys.push_back( std::make_pair("rx_ant", OML_STRING_VALUE)); |
| | 250 | |
| | 251 | _omlKeys.push_back( std::make_pair("channel", OML_UINT32_VALUE)); |
| | 252 | _omlKeys.push_back( std::make_pair("total_samps", OML_UINT32_VALUE)); |
| | 253 | _omlKeys.push_back( std::make_pair("sample_size", OML_UINT32_VALUE)); |
| | 254 | |
| | 255 | _omlKeys.push_back( std::make_pair("rx_freq", OML_DOUBLE_VALUE)); |
| | 256 | _omlKeys.push_back( std::make_pair("rx_rate", OML_DOUBLE_VALUE)); |
| | 257 | _omlKeys.push_back( std::make_pair("rx_gain", OML_DOUBLE_VALUE)); |
| | 258 | _omlKeys.push_back( std::make_pair("SizeFFT", OML_UINT32_VALUE)); |
| | 259 | _omlKeys.push_back( std::make_pair("Bins", OML_BLOB_VALUE)); |
| | 260 | |
| | 261 | oml.start( _omlKeys ); |
| | 262 | }}} |
| | 263 | |
| | 264 | After the OML start has been issued we can set the keyed-value pairs with updated values from sensors. In this section the FFT is |
| | 265 | performed on chunk of 1024 comoplex samples and the magnitude is computed. These get stored in the OML as binary data (oml_blob). |
| | 266 | Once all the keyed-valued pairs are updated with set_key(), they are sented over to the OML server by issuing the insert command. |
| | 267 | This is done repeatedly until the entire buffer has been processed. |
| | 268 | {{{ |
| | 269 | oml.set_key("rx_id", (void*)usrp_info["rx_id"].c_str() ); |
| | 270 | oml.set_key("rx_subdev_name", (void*)usrp_info["rx_subdev_name"].c_str() ); |
| | 271 | oml.set_key("rx_subdev_spec", (void*)usrp_info["rx_subdev_spec"].c_str() ); |
| | 272 | oml.set_key("rx_ant", (void*)usrp_info["rx_ant"].c_str() ); |
| | 273 | |
| | 274 | d64 = (double)(usrp->get_rx_freq() / 1e6); |
| | 275 | oml.set_key("rx_freq", (void*)&d64); |
| | 276 | |
| | 277 | unsigned int nfftBlocks = MultiDeviceBuffer.at(ch).size() / samps_per_buff; |
| | 278 | for (unsigned int j = 0; j < nfftBlocks; j++) |
| | 279 | { |
| | 280 | // copy samples into time_buff for FFT |
| | 281 | memcpy((void*)time_buff.data(), |
| | 282 | (void*)(MultiDeviceBuffer.at(ch).data() + j*samps_per_buff ), |
| | 283 | samps_per_buff * sizeof(std::complex<float>) ); |
| | 284 | |
| | 285 | // Execute FFT. This performs fft on time_buff samples and store results in freq_buff. |
| | 286 | fftwf_execute(fft_p); // FWD FFT is NOT normalized so div by N. |
| | 287 | |
| | 288 | // compute magnitude |
| | 289 | for(unsigned int z = 0; z < signal_mag_instant.size(); z++) |
| | 290 | signal_mag_instant.at(z) = abs(freq_buff.at(z)); |
| | 291 | |
| | 292 | // save the magnitude values as an OML blob |
| | 293 | oml.set_key_blob("Bins",(void*)&signal_mag_instant.front(), |
| | 294 | (unsigned int)signal_mag_instant.size() * sizeof(float) ); |
| | 295 | |
| | 296 | // Send OML measurement to OML server |
| | 297 | oml.insert(); |
| | 298 | } |
| | 299 | |
| | 300 | //oml.stop(); // Don't need this since it is called in the destructor. |
| | 301 | }}} |
| | 302 | |
| | 303 | Once this block completes the connection to the OML server is taken down and no more data can be stored into the database. |
| | 304 | |
| | 305 | |
| | 306 | === View results from OML database === |
| | 307 | |
| | 308 | The database is store in /var/lib/oml2 of the console. Use the command line front-end tool (sqlite3) to query the database direct |
| | 309 | ly especially for binary data. For a detailed overview on sqlite3 CLI please refer http://www.sqlite.org/cli.html. |
| | 310 | |
| | 311 | An application has been made to parse this database. This application is very specific to this tutorial. If the format of the data |
| | 312 | base table changes, the application may no longer be able to parse the database file. |
| | 313 | Display all the tables in the database. |
| | 314 | {{{ |
| | 315 | console> ./db_parse --file /var/lib/oml2/rx_multi_samples.sq3 --showtables |
| | 316 | |
| | 317 | name = _senders |
| | 318 | |
| | 319 | name = _experiment_metadata |
| | 320 | |
| | 321 | name = _mp__rx_multi_samples |
| | 322 | |
| | 323 | Use following command to retrieve contents of table. |
| | 324 | ./db_parse --file /var/lib/oml2/rx_multi_samples.sq3 --exec "SELECT * FROM <table>" |
| | 325 | |
| | 326 | To retrieve contents of the blob: |
| | 327 | ./db_parse --file /var/lib/oml2/rx_multi_samples.sq3 --blob2bin ch0_data.bin --ch 0 --table _mp__rx_multi_samples --key Bins |
| | 328 | |
| | 329 | }}} |
| | 330 | |
| | 331 | |
| | 332 | We can recover the contents of the FFT data for each channel and save them into a binary file for viewing in octave. |
| | 333 | {{{ |
| | 334 | console> ./db_parse --file /var/lib/oml2/rx_multi_samples.sq3 --blob2bin ch0_data.bin --ch 0 --table _mp__rx_multi_samples --key |
| | 335 | Bins |
| | 336 | |
| | 337 | Executing query statement |
| | 338 | select LENGTH(Bins),Bins from _mp__rx_multi_samples where channel=0 |
| | 339 | }}} |
| | 340 | |
| | 341 | In the console we should have a binary file by the name ch0_data.bin. This can be loaded and viewed in ocatve. |
| | 342 | {{{ |
| | 343 | octave> mag0 = fReadBinaryFile('ch0_data.bin','single'); |
| | 344 | octave> mag0 = reshape(mag0, 1024, length(mag0)/1024); |
| | 345 | octave> image(mag0*1000) |
| | 346 | }}} |
| | 347 | |
| | 348 | Upload image of mag0... |
| | 349 | |