User Tools

Site Tools


ris_operation_tutorial

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
ris_operation_tutorial [2026/09/03 15:25] cmorinris_operation_tutorial [2026/09/04 14:42] (current) cmorin
Line 1: Line 1:
 # RIS Operation Tutorial # RIS Operation Tutorial
- 
-To Fill In 
  
 **This Tutorial assumes that you have followed the basic CorteXlab operation tutorials (at least [[running_your_first_experiment|GNU Radio benchmark example]], and [[bokehgui_for_cortexlab|Eyes and ears inside CorteXlab]], but [[running_your_task_interactively|GNU Radio benchmark, interactive command execution]] and [[gnu_radio_docker_benchmark_example|GNU Radio benchmark example with docker]] are recommended)** **This Tutorial assumes that you have followed the basic CorteXlab operation tutorials (at least [[running_your_first_experiment|GNU Radio benchmark example]], and [[bokehgui_for_cortexlab|Eyes and ears inside CorteXlab]], but [[running_your_task_interactively|GNU Radio benchmark, interactive command execution]] and [[gnu_radio_docker_benchmark_example|GNU Radio benchmark example with docker]] are recommended)**
Line 53: Line 51:
   * Configuration algorithms:   * Configuration algorithms:
      * `/ref_optimization` that tries many configurations and iterates on them based on feedback from a receiver      * `/ref_optimization` that tries many configurations and iterates on them based on feedback from a receiver
-      `/narrow_beamforming` that generates a configuration for a narrow_beam based on geometric parameters+     * `/narrow_beamforming` that generates a configuration for a narrow_beam based on geometric parameters
  
   * Managing configuration files with `/load_file_conf`, `/read_file_conf`, and `/write_file_conf` to replay, read, and write previously optimized pixel configurations stored on the control node   * Managing configuration files with `/load_file_conf`, `/read_file_conf`, and `/write_file_conf` to replay, read, and write previously optimized pixel configurations stored on the control node
Line 158: Line 156:
  
 ### (Optional) Explore the GRC files ### (Optional) Explore the GRC files
-Show what's inside the TX and RX files. +Let's see what'in the scripts we are going to run.  
-Quick for TX +You can look inside the .py file or open the .grc files in your own installation of GNU Radio Companion but here are some screenshots: 
-For RX, point to the feedback snippet and the http_helper block, showing how they work. + 
-Useful because these are the elements users would need to reuse for their own experiments+{{ ::ris_tuto_tx_grc_file.png?direct&400 |}} 
 + 
 +The TX side is really simple, we generate a sequence (here a Zadoff-Chu of 2048 samples) that we send on repeat to the radio block. 
 + 
 +{{ ::ris_tuto_rx_grc_file.png?direct&400 |}} 
 + 
 +On the RX sidewe display first the raw received frequencies, and then we compute the received power, averaged over the sequence's length, converted to dB and displayed on the number sink. 
 +On top of that, we have quite a few elements to interact with the RIS. 
 + 
 +The main one sits inside the HTTP Helper block. It's a custom Python block to make the HTTP requests to the RIS server. Its code is inside ''power\_reader\_epy\_block\_0.py'' and looks like this: 
 + 
 +<code> 
 +class http_helper(gr.sync_block):  # other base classes are basic_blockdecim_block, interp_block 
 +    """Embedded Python Block example - a simple multiply const""" 
 + 
 +    def __init__(self, ris_node=1.0):  # only default arguments here 
 +        """arguments to this function show up as parameters in GRC""" 
 +        gr.sync_block.__init__( 
 +            self, 
 +            name='HTTP Helper',   # will show up in GRC 
 +            in_sig=[], 
 +            out_sig=[] 
 +        ) 
 +        # if an attribute with the same name as a parameter is found, 
 +        # a callback is registered (properties work, too)
 +        self.ris_node = ris_node 
 + 
 +        self.my_log = gr.logger(self.alias()) 
 + 
 +    # def work(self, input_items, output_items): 
 +    #     """example: multiply with constant""" 
 +    #     output_items[0][:] = input_items[0] * self.example_param 
 +    #     return len(output_items[0]) 
 +     
 +    def turn_on(self): 
 +        r = requests.get(f"http://mnode{self.ris_node}:5000/turn_on"
 +        print(f"{r.text}"
 +        return r.text 
 +         
 + 
 +    def turn_off(self): 
 +        r = requests.get(f"http://mnode{self.ris_node}:5000/turn_off"
 +        print(f"{r.text}"
 +        return r.text 
 +         
 +     
 + 
 +    def reset(self, val=1): 
 +        r = requests.post(f"http://mnode{self.ris_node}:5000/set_pixels", json={"pixels": [val for i in range(128)]}) 
 +        if r.status_code != requests.codes.ok: 
 +            print(f"{float(r.text)}"
 +            return float(r.text) 
 +        return 
 + 
 +    def ref_optim(self, optim_node, loops, init_configs=100): 
 +        self.my_log.warn(f"Launching optimisation for node {optim_node}, {loops} loops and {init_configs} initial configs"
 + 
 +        req_url = f'curl "http://mnode{self.ris_node}:5000/ref_optimization?power_server=mnode{optim_node}:5002&loops={loops}&init_configs={init_configs}"' 
 +        self.my_log.warn(req_url) 
 +        subprocess.Popen(req_url, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) 
 + 
 + 
 +    def load_config(self): 
 +        r = requests.get(f"http://mnode{self.ris_node}:5000/load_file_conf"
 +        print(r.text) 
 + 
 +    def get_config(self): 
 +        response = requests.get(f"http://mnode{self.ris_node}:5000/read_file_conf"
 +        self.my_log.warn(f"{response=}"
 +        vector = response.json() 
 +        ris_c = np.array(vector['best_config']) 
 +        self.my_log.warn(ris_c) 
 +  
 +    def Beamform(self,RX_azimuth,RX_elevation,TX_distance,TX_azimuth,TX_elevation): 
 +        params = { 
 +            "RX_azimuth" : RX_azimuth, 
 +            "RX_elevation" : RX_elevation, 
 +            "TX_distance" : TX_distance, 
 +            "TX_azimuth" : TX_azimuth, 
 +            "TX_elevation" : TX_elevation 
 +        } 
 +         
 +        response = requests.get(f"http://mnode{self.ris_node}:5000/narrow_beamforming", params=params) 
 +        self.my_log.warn(response) 
 +        self.my_log.warn(f"Angle Tx = {TX_elevation} RX = {RX_elevation}"
 +</code> 
 +All its functions send HTTP requests through the ''requests'' module to the relevant API endpoints on the RIS server, except for the ''ref\_optim'' function where a ''curl'' subprocess is used to avoid freezing the display while the request runs. 
 + 
 +As you can see with their parameters, all the Bokeh GUI Button blocks are there to call functions of this HTTP Helper block. 
 + 
 + 
 +All this is sufficient for all RIS commands, except for, again, the ''/ref\_optimization'' endpoint. 
 +This one requires feedback to evaluate the configuration it tries, in the form of another HTTP server, replying with a float value, whenever queried on the ''/power\_feedback'' endpoint. 
 + 
 +In our case, it's implemented inside of the same RX flowgraph, as a python snippet this time, that contains the following: 
 +<code> 
 +from flask import Flask 
 +import threading 
 + 
 +def server(): 
 +    app = Flask(__name__) 
 + 
 +    @app.route('/power_feedback', methods=['GET']) 
 +    def power_feedback(): 
 + 
 +        read_power = self.blocks_probe_signal_x_0.level() 
 +        return f"{read_power}" 
 + 
 +    app.run(port=5002, host="0.0.0.0"
 + 
 + 
 +self.my_log = gr.logger(self.alias()) 
 +server_thread = threading.Thread(target=server) 
 +server_thread.daemon = True 
 +server_thread.start() 
 +</code> 
 + 
 +We setup a Flask server inside of a separate thread, that implements only the ''/power\_feedback'' endpoint. 
 +And that endpoint queries the Probe Signal block at the end of the flowgraph to respond with the latest computed average received power. 
 + 
 +Finally, the Fast Multiply Const block just before the Probe Signal uses the value of the Bokeh GUI Checkbox to inverse the signal metric when the box is checked.
  
 ### Run the task ### Run the task
-TODO: set the scenario so that it installs flask and does the proper setupDirectly run the commands, no connecting over ssh+ 
 +We will use the usual commands to run the task: 
 +<code> 
 +you@srvairlock:~/Tutorials/Tuto_RIS/ris-api/examples/power_feedback minus task create scenario  
 +Creating the task file... 
 +Task file scenario.task created successfully. 
 +you@srvairlock:~/Tutorials/Tuto_RIS/ris-api/examples/power_feedback minus task submit scenario.task 
 +25062 
 +</code>
  
 ### Connect browser to the display ### Connect browser to the display
-Take the opportunity to show the socks proxy connection system, it's so useful 
  
-Get to displaying the bokeh interface, and show the operation when we click on the buttons.+In the previous tutorial about remote monitoring[[bokehgui_for_cortexlab|Eyes and ears inside CorteXlab]], we used a direct ssh connection to the node with port forwarding to be able to point our browser to the bokehgui server running inside of the platform. 
 + 
 +Here, we'll show an other option, that requires a bit more setup, but allows for more flexibility once it's done: SOCKS proxy. 
 + 
 +If you don't want to use that option, or if it doesn't work for you, you can always fall back to the port forwarding method. 
 +You would simply need to start an ssh deamon on node 18 to be able to connect to it. 
 +For that, you need to add an extra exec line on the node, like so: 
 + 
 +<code> 
 +... 
 +nodes: 
 +  node18: 
 +    container: 
 +    - image: ghcr.io/cortexlab/cxlb-gnuradio-3.10:1.5 
 +      exec:  
 +      - /usr/sbin/sshd -p 2222 -D 
 +      - bash -lc "pip install flask && apt install curl" 
 +      command: bash -lc "python3 /cortexlab/homes/{YOUR USERNAME}/Tutorials/Tuto_RIS/ris-api/examples/power_feedback/power_reader.py -r 5e6" 
 +... 
 +</code> 
 + 
 +Back to the SOCKS proxy method. 
 +We first need to open that SSH proxy with an extra option to the SSH command so, in a new terminal: 
 + 
 +<code> 
 +you@yourpc:~$ ssh username@gw.cortexlab.fr -D 4321 
 +</code> 
 + 
 +Proxy is now open on port 4321. 
 +What's left is to configure your browser to use it. 
 +Many websites explain how to do it for many browsers, better than we could do here, for instance, [[https://www.proxiesthatwork.com/guides/setup-browsers|this one]] 
 +The port to setup is **4321**, same as we specified with the ssh -D option. 
 +And the proxy is running locally, so the server address is **127.0.0.1** 
 + 
 +Extensions are also available to make the proxy configuration and switching easier, such as [[https://getfoxyproxy.org/help/proxy/|FoxyProxy]] 
 + 
 +Once the configuration is done, provided the task is still running, you can connect to it by pointing your browser to the node's URL: 
 +''http://mnode18:5006/'' 
 + 
 +It should show an interface similar to this with a bunch of controls on the left, a frequency response plot, and a Received power plot: 
 + 
 +{{ ::ris_tuto_disp_base.png?direct&400 |}} 
 + 
 +First turn on the RIS by clicking on the corresponding button. It's normal that the plots don't change at this point. 
 +Then click on the ''Optimize RIS'' button. 
 +You should see the plots moving up and down rapidly over a few seconds, eventually settling a few dB higher than where it started. You did your first RIS configuration, well done! 
 + 
 +To see the difference, with the default state, either turn off the RIS (don't forget to turn it back on after that), or click on the ''Reset RIS'' button, it manually sets all the pixels to their default state without having to reboot the board. 
 + 
 +{{ ::ris_tuto_disp_after_optim.png?direct&400 |}} 
 + 
 +With the ''Load Config'' button, you can reapply the previously optimised configuration without having to redo the optimisation process. 
 + 
 +You can play with the ''Optim loops'' and ''Initial configs'' parameters to tell the optimisation algorithm to try more (or less) configurations, changing the time required for the process, but also affecting the end result. 
 + 
 +On the right of each of the two plots, the toolbar contains a button to reset the max value line. It's the third one from the bottom, with a tooltip reading appropriately ''Reset Max''
 + 
 + 
 +Feel free to play with all the parameters such as the receive gain, TX/RX frequency or, to see more dramatic results, reverse the optimisation, telling the RIS to reduce the received power instead of increasing it. 
 + 
 + 
 +The Elevation, Azimuth and Distance parameters go with the Beamform button. It triggers the narrow beam beam forming algorithm, setting a configuration based on those geometric parameters. 
 + 
 +For Both algorithms, you can get the optimised configuration with the ''Get ris configuration'' button. It is very simple, though, so it will just print the pixel array on stdout that you can look at in the results folder.
  
 ## Conclusion ## Conclusion
  
 +This tutorial and its code demonstrates the remote RIS operation, with all its available commands.
 +Here, everything is done inside a GNU Radio flowgraph but, since since communication goes through standard HTTP, you could use raw python, or whatever language you prefer.
 +Call could even be made on the terminal, from airlock or your own (with the SOCKS proxy and a CLI utility like tsocks), or from the browser debug menu.
ris_operation_tutorial.1788449134.txt.gz · Last modified: by cmorin

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki