| 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 | |