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 14:43] 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 17: Line 15:
 It is in the form of a flat square 40cm on a side and designed to operate on a wide 800MHz band centered around 3.7 GHz with a 120° field of view on both azimuth and elevation. It is in the form of a flat square 40cm on a side and designed to operate on a wide 800MHz band centered around 3.7 GHz with a 120° field of view on both azimuth and elevation.
  
-It contains 128 reflective elements, with one bit control (on/off), arranged in pairs in a 8x8 square. One element of the pair handling vertical polarisation, the other horizontal.+It contains 128 reflective elements (pixels), with one bit control (on/off), arranged in pairs in a 8x8 square. One element of the pair handling vertical polarisation, the other horizontal.
  
 You can find more technical information about it and request their specsheet on [[https://www.greenerwave.com/products/ris-fr1-fr2-fr3/|their website]]. You can find more technical information about it and request their specsheet on [[https://www.greenerwave.com/products/ris-fr1-fr2-fr3/|their website]].
Line 36: Line 34:
 To show a more interesting, non line-of-sight scenario, some wall panels with RF absorbing foam have been setup between nodes 35 and 39 (again, see the map). To show a more interesting, non line-of-sight scenario, some wall panels with RF absorbing foam have been setup between nodes 35 and 39 (again, see the map).
 As for the RIS itself, they are designed to be relocated, so don't hesitate to tell us if you need them elsewhere. As for the RIS itself, they are designed to be relocated, so don't hesitate to tell us if you need them elsewhere.
- 
  
 ## How it works ## How it works
Line 43: Line 40:
 It is connected to a node via USB, so booking that node is necessary to have it power on and controlled. It is connected to a node via USB, so booking that node is necessary to have it power on and controlled.
  
-We provide a +We provide a ready to use docker image that need to run on the node to serve as driver: 
 + 
 +`registry.gitlab.inria.fr/cortexlab/measurements/ris-api/ris:1.1` 
 + 
 +When instantiated, it automatically starts a small web server on port 5000 serving a REST API for remote control and feedback from any of the other nodes, or even (through a proxy), your own computer. 
 + 
 +The available controls are: 
 +  * Turn on/off with `/turn_on` and `/turn_off` (The off state corresponds to setting all ones on the pixels) 
 +  * Manually setting the pixel states with `/set_pixels`, with a list 128 of 0 and/or 1 
 +  * Configuration algorithms: 
 +     * `/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 
 + 
 +  * 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 
 + 
 + 
 +  
 +For more details on this API, the code and a detailed readme is hosted in [[https://gitlab.inria.fr/cortexlab/measurements/ris-api|this repository]].
  
  
 ## The scenario ## The scenario
 +
 +In this example scenario to demonstrate basic operation, we will do the following based on the room setup:
 +
 +  * Setup node 38 to continuously transmit a known sequence over a 5MHz bandwidth
 +  * 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.
 +
 +
  
  
 ## 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.1788360216.txt.gz · Last modified: by cmorin

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki