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/02 15:57] – [The scenario] 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 69: Line 67:
   * Setup the RIS node with the provided docker image to allow for RIs control   * Setup the RIS node with the provided docker image to allow for RIs control
   * Setup node 18, on the other side of the wall, to receive the signal and display received power and channel frequency response information, as well as buttons to send command to the RIS node.   * Setup node 18, on the other side of the wall, to receive the signal and display received power and channel frequency response information, as well as buttons to send command to the RIS node.
- 
  
  
Line 76: Line 73:
 ## Running it ## Running it
  
 +
 +### Get the files
 +
 +The files we are going to use live inside the same repository as the REST API.
 +Let's go to the tutorial folder to get those:
 +
 +
 +<code>
 +you@srvairlock:~$ mkdir -r Tutorials/Tuto_RIS
 +you@srvairlock:~$ cd Tutorials/Tuto_RIS
 +you@srvairlock:~/Tutorials/Tuto_RIS git clone https://gitlab.inria.fr/cortexlab/measurements/ris-api.git
 +you@srvairlock:~/Tutorials/Tuto_RIS cd ris-api
 +</code>
 +
 +
 +We will be using the contents of the examples/power_feedback folder
 +
 +
 +<code>
 +you@srvairlock:~/Tutorials/Tuto_RIS/ris-api cd examples/power_feedback
 +you@srvairlock:~/Tutorials/Tuto_RIS/ris-api/examples/power_feedback ls
 +power_reader_epy_block_0.py  power_reader.grc  power_reader.py  power_tx.grc  power_tx.py  scenario
 +</code>
 +
 +Let's go over each one of the files in this folder:
 +
 +  * ''power\_tx.grc'': the GNU Radio Companion flowgraph description for the transmitter
 +  * ''power\_reader.grc'': the GNU Radio Companion flowgraph description for the receiver
 +  * ''power\_tx.py'': the GNU Radio python script for the transmitter
 +  * ''power\_reader.py'': the GNU Radio python script for the receiver
 +  * ''power\_reader\_epy\_block\_0.py'': Helper code for the receiver (code for communication with the RIS)
 +  * ''scenario'': The folder containing the scenario description file that we will edit
 +
 +Edit the scenario file to point the commands to the user's folder
 +
 +Let's open the existing scenario file using nano (or some installed text editor that you may prefer):
 +
 +<code>
 +you@srvairlock:~/Tutorials/Tuto_RIS/ris-api/examples/power_feedback nano scenario/scenario.yaml
 +</code>
 +
 +<code>
 +# Scenario textual description
 +description: Power monitoring tutorial for use with RIS
 +
 +# Experiment maximum duration
 +duration: 1800
 +
 +
 +nodes:
 +  node18:
 +    container:
 +    - image: ghcr.io/cortexlab/cxlb-gnuradio-3.10:1.5
 +      exec: 
 +      - 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"
 +  node38:
 +    container:
 +    - image: ghcr.io/cortexlab/cxlb-gnuradio-3.10:1.5
 +      command: bash -lc "python3 /cortexlab/homes/{YOUR USERNAME}/Tutorials/Tuto_RIS/ris-api/examples/power_feedback/power_tx.py -r 5e6"
 +
 +  node31:
 +    container:
 +    - image: registry.gitlab.inria.fr/cortexlab/measurements/ris-api/ris:1.1
 +    passive: true
 +</code>
 +
 +**Make sure you edit the command paths for TX and RX to point to your own folder (replace with your username)**
 +
 +As you can see here, the scenario file is very similar to what was used in previous tutorials.
 +For node 31, driving the RIS itself, the only thing required is to specify the ris-api docker image.
 +No need for a command, server startup is automatic.
 +
 +The only exotic element here would be for node 18:
 +<code>
 +      exec: 
 +      - bash -lc "pip install flask && apt install curl"
 +</code>
 +This option allows for execution of extra commands in parallel of the main one.
 +We use it to install flask and curl, two packages we want to use to communicate with the RIS, on the fly, without having to generate a dedicated docker image.
 +
 +
 +### (Optional) Explore the GRC files
 +Let's see what's in the scripts we are going to run. 
 +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:
 +
 +{{ ::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 side, we 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_block, decim_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
 +
 +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
 +
 +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.1788364661.txt.gz · Last modified: by cmorin

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki