ris_operation_tutorial
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| ris_operation_tutorial [2025/11/25 15:51] – cmorin | ris_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]], | **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]], | ||
| - | Here, we will go through an experiment using and operating the RIS installed in the CorteXlab room, that should showcase all that is necessary to drive it for your own experiments. | + | Here, we will go through an experiment using and operating the reflective intelligent surface (RIS) installed in the CorteXlab room, that should showcase all that is necessary to drive it for your own experiments. |
| + | We assume prior knowlegde of the concept of RIS and its uses. We focus on the technical operation of the one we have installed in the room. | ||
| + | . | ||
| ## The RIS | ## The RIS | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | The RIS currently installed in SLICES/ | ||
| + | |||
| + | 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 (pixels), with one bit control (on/off), arranged in pairs in a 8x8 square. One element of the pair handling vertical polarisation, | ||
| + | |||
| + | You can find more technical information about it and request their specsheet on [[https:// | ||
| + | |||
| ## The room setup | ## The room setup | ||
| //Please check with the CorteXlab Team for changes to that setup (and to check if it has changed)// | //Please check with the CorteXlab Team for changes to that setup (and to check if it has changed)// | ||
| + | |||
| + | {{ :: | ||
| + | |||
| + | The RIS is currently hanging off the ceiling railings, inbetween nodes 31 and 33, and pointing to the " | ||
| + | |||
| + | It is connected to node 31 via USB for power and control, so we will have to use that node to drive it. | ||
| + | |||
| + | The RIS installation is designed to be easy to relocate so feel free to contact the CorteXlab team if you want to run experiments with the RIS in a different location (we may need to plug it in a different node). | ||
| + | |||
| + | |||
| + | To show a more interesting, | ||
| + | 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 | ||
| + | |||
| + | The RIS itself can not be directly booked, and a low level driver is not made available interact with it. | ||
| + | It is connected to a node via USB, so booking that node is necessary to have it power on and controlled. | ||
| + | |||
| + | We provide a ready to use docker image that need to run on the node to serve as driver: | ||
| + | |||
| + | `registry.gitlab.inria.fr/ | ||
| + | |||
| + | When instantiated, | ||
| + | |||
| + | 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 `/ | ||
| + | * Configuration algorithms: | ||
| + | * `/ | ||
| + | * `/ | ||
| + | |||
| + | * Managing configuration files with `/ | ||
| + | |||
| + | |||
| + | |||
| + | For more details on this API, the code and a detailed readme is hosted in [[https:// | ||
| ## 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, | ||
| + | |||
| + | |||
| ## 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: | ||
| + | |||
| + | |||
| + | < | ||
| + | you@srvairlock: | ||
| + | you@srvairlock: | ||
| + | you@srvairlock: | ||
| + | you@srvairlock: | ||
| + | </ | ||
| + | |||
| + | |||
| + | We will be using the contents of the examples/ | ||
| + | |||
| + | |||
| + | < | ||
| + | you@srvairlock: | ||
| + | you@srvairlock: | ||
| + | power_reader_epy_block_0.py | ||
| + | </ | ||
| + | |||
| + | Let's go over each one of the files in this folder: | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | 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): | ||
| + | |||
| + | < | ||
| + | you@srvairlock: | ||
| + | </ | ||
| + | |||
| + | < | ||
| + | # Scenario textual description | ||
| + | description: | ||
| + | |||
| + | # Experiment maximum duration | ||
| + | duration: 1800 | ||
| + | |||
| + | |||
| + | nodes: | ||
| + | node18: | ||
| + | container: | ||
| + | - image: ghcr.io/ | ||
| + | exec: | ||
| + | - bash -lc "pip install flask && apt install curl" | ||
| + | command: bash -lc " | ||
| + | node38: | ||
| + | container: | ||
| + | - image: ghcr.io/ | ||
| + | command: bash -lc " | ||
| + | |||
| + | node31: | ||
| + | container: | ||
| + | - image: registry.gitlab.inria.fr/ | ||
| + | passive: true | ||
| + | </ | ||
| + | |||
| + | **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: | ||
| + | < | ||
| + | exec: | ||
| + | - bash -lc "pip install flask && apt install curl" | ||
| + | </ | ||
| + | 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: | ||
| + | |||
| + | {{ :: | ||
| + | |||
| + | 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. | ||
| + | |||
| + | {{ :: | ||
| + | |||
| + | On the RX side, we display first the raw received frequencies, | ||
| + | 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 '' | ||
| + | |||
| + | < | ||
| + | class http_helper(gr.sync_block): | ||
| + | """ | ||
| + | |||
| + | def __init__(self, | ||
| + | """ | ||
| + | gr.sync_block.__init__( | ||
| + | self, | ||
| + | name=' | ||
| + | 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, | ||
| + | # """ | ||
| + | # | ||
| + | # | ||
| + | | ||
| + | def turn_on(self): | ||
| + | r = requests.get(f" | ||
| + | print(f" | ||
| + | return r.text | ||
| + | | ||
| + | |||
| + | def turn_off(self): | ||
| + | r = requests.get(f" | ||
| + | print(f" | ||
| + | return r.text | ||
| + | | ||
| + | | ||
| + | |||
| + | def reset(self, val=1): | ||
| + | r = requests.post(f" | ||
| + | if r.status_code != requests.codes.ok: | ||
| + | print(f" | ||
| + | return float(r.text) | ||
| + | return | ||
| + | |||
| + | def ref_optim(self, | ||
| + | self.my_log.warn(f" | ||
| + | |||
| + | req_url = f'curl " | ||
| + | self.my_log.warn(req_url) | ||
| + | subprocess.Popen(req_url, | ||
| + | |||
| + | |||
| + | def load_config(self): | ||
| + | r = requests.get(f" | ||
| + | print(r.text) | ||
| + | |||
| + | def get_config(self): | ||
| + | response = requests.get(f" | ||
| + | self.my_log.warn(f" | ||
| + | vector = response.json() | ||
| + | ris_c = np.array(vector[' | ||
| + | self.my_log.warn(ris_c) | ||
| + | |||
| + | def Beamform(self, | ||
| + | params = { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | | ||
| + | response = requests.get(f" | ||
| + | self.my_log.warn(response) | ||
| + | self.my_log.warn(f" | ||
| + | </ | ||
| + | All its functions send HTTP requests through the '' | ||
| + | |||
| + | 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 ''/ | ||
| + | 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 ''/ | ||
| + | |||
| + | In our case, it's implemented inside of the same RX flowgraph, as a python snippet this time, that contains the following: | ||
| + | < | ||
| + | from flask import Flask | ||
| + | import threading | ||
| + | |||
| + | def server(): | ||
| + | app = Flask(__name__) | ||
| + | |||
| + | @app.route('/ | ||
| + | def power_feedback(): | ||
| + | |||
| + | read_power = self.blocks_probe_signal_x_0.level() | ||
| + | return f" | ||
| + | |||
| + | app.run(port=5002, | ||
| + | |||
| + | |||
| + | self.my_log = gr.logger(self.alias()) | ||
| + | server_thread = threading.Thread(target=server) | ||
| + | server_thread.daemon = True | ||
| + | server_thread.start() | ||
| + | </ | ||
| + | |||
| + | We setup a Flask server inside of a separate thread, that implements only the ''/ | ||
| + | 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: | ||
| + | < | ||
| + | you@srvairlock: | ||
| + | Creating the task file... | ||
| + | Task file scenario.task created successfully. | ||
| + | you@srvairlock: | ||
| + | 25062 | ||
| + | </ | ||
| + | |||
| + | ### Connect browser to the display | ||
| + | |||
| + | In the previous tutorial about remote monitoring, [[bokehgui_for_cortexlab|Eyes and ears inside CorteXlab]], | ||
| + | |||
| + | 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' | ||
| + | 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: | ||
| + | |||
| + | < | ||
| + | ... | ||
| + | nodes: | ||
| + | node18: | ||
| + | container: | ||
| + | - image: ghcr.io/ | ||
| + | exec: | ||
| + | - / | ||
| + | - bash -lc "pip install flask && apt install curl" | ||
| + | command: bash -lc " | ||
| + | ... | ||
| + | </ | ||
| + | |||
| + | 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: | ||
| + | |||
| + | < | ||
| + | you@yourpc: | ||
| + | </ | ||
| + | |||
| + | 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:// | ||
| + | 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:// | ||
| + | |||
| + | 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: | ||
| + | '' | ||
| + | |||
| + | 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: | ||
| + | |||
| + | {{ :: | ||
| + | |||
| + | 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 '' | ||
| + | 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, | ||
| + | |||
| + | 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 '' | ||
| + | |||
| + | {{ :: | ||
| + | |||
| + | With the '' | ||
| + | |||
| + | You can play with the '' | ||
| + | |||
| + | 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 '' | ||
| + | |||
| + | |||
| + | 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, | ||
| + | |||
| + | |||
| + | 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 '' | ||
| ## 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.1764085916.txt.gz · Last modified: by cmorin
