diff --git a/README.md b/README.md index 5254ae3..512538b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- pyX2Cscope Logo + pyX2Cscope Logo

# pyX2Cscope @@ -55,7 +55,7 @@ print(speed_measured.get_value()) speed_reference.set_value(1000) ``` -Further [Examples](https://github.com/X2Cscope/pyx2cscope/tree/main/pyx2cscope/examples) directory in the pyX2Cscope project to check out the available examples or create a new .py file according to your requirements. +Check [Examples](https://github.com/X2Cscope/pyx2cscope/tree/main/pyx2cscope/examples) directory in the pyX2Cscope project to see common uses of this library. ## Development diff --git a/doc/conf.py b/doc/conf.py index 9e4207c..995b02b 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -34,41 +34,33 @@ "sphinx.ext.viewcode", "sphinx.ext.graphviz", "sphinx.ext.autosummary", + "sphinx.ext.intersphinx", "autoapi.extension", ] autoapi_dirs = ["../pyx2cscope"] autoapi_ignore = [ "*/examples/*", - "*/gui/*", # For now, we ignore this, TODO: fix this + "*/gui/*", # For now, we ignore this ] suppress_warnings = [ - "autoapi.python_import_resolution" -] # Suppress warnings about unresolved imports TODO: fix this + "autoapi.python_import_resolution" +] # Suppress warnings about unresolved imports nitpick_ignore = [ - ("py:class", "numbers.Number"), + ("py:class", "pyx2cscope.gui"), + ("py:class", "mchplnet.lnet"), ("py:class", "mchplnet.lnet.LNet"), - ("py:class", "mchplnet.lnet"), # For now, we ignore this, TODO: fix this - ( - "py:class", - "pyx2cscope.variable.variable.LNet", - ), # For now, we ignore this, TODO: fix this - ("py:obj", "abc.ABC"), # For now, we ignore this, TODO: fix this - ("py:class", "enum.Enum"), # For now, we ignore this, TODO: fix this - ("py:class", "abc.ABC"), # For now, we ignore this, TODO: fix this - ("py:class", "PyQt5.QtWidgets.QMainWindow"), - ( - "py:class", - "mchplnet.services.scope.ScopeChannel", - ), # For now, we ignore this, TODO: fix this - ( - "py:class", - "mchplnet.interfaces.abstract_interface.InterfaceABC", - ), # For now, we ignore this, TODO: fix this + ("py:class", "mchplnet.services.scope.ScopeChannel"), + ("py:class", "mchplnet.interfaces.factory.InterfaceType"), + ("py:class", "mchplnet.interfaces.abstract_interface.InterfaceABC"), ] +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), +} + graphviz_output_format = "png" # 'svg' is also possible templates_path = ["_templates"] diff --git a/doc/development.md b/doc/development.md index 0855e80..b789ff7 100644 --- a/doc/development.md +++ b/doc/development.md @@ -6,6 +6,8 @@ There is a mailing group to receive your inputs and questions: MotorControl@micr ## Contribution Contribute to source code, documentation, examples and report issues: https://github.com/X2Cscope/pyx2cscope +If you want to contribute to the project, please follow these steps: + 1. Fork the pyX2Cscope repository. 2. Create a new branch for your changes. 3. Make the necessary changes and commit them. @@ -22,8 +24,10 @@ Create virtual envrionment with make venv git clone https://github.com/X2Cscope/pyx2cscope.git cd pyx2cscope python -m venv .venv + #Windows .venv\Scripts\activate + #linux source .venv\bin\activate ``` @@ -55,4 +59,4 @@ sphinx-build -M html doc build --keep-going ## Creating executables ```bash pyinstaller --noconfirm .\pyx2cscope_win.spec -``` \ No newline at end of file +``` diff --git a/doc/gui_qt.md b/doc/gui_qt.md index 23c6bef..6082929 100644 --- a/doc/gui_qt.md +++ b/doc/gui_qt.md @@ -20,10 +20,10 @@ python -m pyx2cscope -q ## Getting Started with pyX2Cscope reference GUI ## Tab: WatchPlot -![WatchPlot](https://raw.githubusercontent.com/X2Cscope/pyx2cscope/refs/heads/main/pyx2cscope/gui/img/NewGui.jpg) +![WatchPlot](https://raw.githubusercontent.com/X2Cscope/pyx2cscope/refs/heads/main/doc/images/gui_watch_plot.jpg) 1. pyX2Cscope-GUI is based on Serial interface. 2. The Firmware of the microcontroller should have the X2Cscope library/Peripheral enabled. -3. in Tab WatchPlot, five channels values can be viewed, modified and can be plotted in the plot window. +3. In Tab WatchPlot, five channels values can be viewed, modified and can be plotted in the plot window. 4. In COM Port, either select **Auto Connect** or select the appropriate COM Port, Baud Rate from the drop-down menus and the ELF file of the project, the microcontroller programmed with.
5. Sample time can be changed during run time as well, by default its set to 500 ms. 6. Press on **Connect** @@ -31,7 +31,7 @@ python -m pyx2cscope -q 8. Information related to the microcontroller will be displayed in the top-left corner. ## Tab: ScopeView -![ScopeView](https://raw.githubusercontent.com/X2Cscope/pyx2cscope/refs/heads/main/pyx2cscope/gui/img/NewGui2.jpg) +![ScopeView](https://raw.githubusercontent.com/X2Cscope/pyx2cscope/refs/heads/main/doc/images/gui_scope_view.jpg) 1. ScopeView supports up to 8 PWM resolution channels for precise signal control. 2. You can configure all trigger settings directly within the window. To enable the trigger for a variable, check the corresponding trigger checkbox. @@ -40,7 +40,7 @@ python -m pyx2cscope -q 5. To zoom in on the plot, left-click and drag on the desired area. To return to the original view, right-click and select View All. ## Tab: WatchView -![WatchView](https://raw.githubusercontent.com/X2Cscope/pyx2cscope/refs/heads/main/pyx2cscope/gui/img/NewGui3.jpg) +![WatchView](https://raw.githubusercontent.com/X2Cscope/pyx2cscope/refs/heads/main/doc/images/gui_watch_view.jpg) 1. WatchView lets users add or remove variables as needed. To remove a variable, click the Remove button next to it. 2. Users can visualize variables in live mode with an update rate of 500 milliseconds. This rate is the default setting and cannot be changed. diff --git a/doc/gui_web.md b/doc/gui_web.md index 26b9e41..0043dff 100644 --- a/doc/gui_web.md +++ b/doc/gui_web.md @@ -2,10 +2,8 @@ The Web Graphic User Interface is implemented using Flask, bootstrap 4, jquery and chart.js It is also an example of how to build a custom GUI using pyX2Cscope. -This interface allows -you to use multiple windows or even access functions from smart devices. -The server runs -by default on your local machine and does not allow external access. +This interface allows you to use multiple windows or even access functions from smart devices. +The server runs by default on your local machine and does not allow external access. The server has default port 5000 and will be accessible on http://localhost:5000 ## Starting the Web GUI diff --git a/doc/images/NewGui4.jpg b/doc/images/NewGui4.jpg deleted file mode 100644 index d0b8fd5..0000000 Binary files a/doc/images/NewGui4.jpg and /dev/null differ diff --git a/doc/images/Setting.jpg b/doc/images/Setting.jpg deleted file mode 100644 index b8c849b..0000000 Binary files a/doc/images/Setting.jpg and /dev/null differ diff --git a/doc/images/NewGui2.jpg b/doc/images/gui_scope_view.jpg similarity index 100% rename from doc/images/NewGui2.jpg rename to doc/images/gui_scope_view.jpg diff --git a/doc/images/NewGui.jpg b/doc/images/gui_watch_plot.jpg similarity index 100% rename from doc/images/NewGui.jpg rename to doc/images/gui_watch_plot.jpg diff --git a/doc/images/NewGui3.jpg b/doc/images/gui_watch_view.jpg similarity index 100% rename from doc/images/NewGui3.jpg rename to doc/images/gui_watch_view.jpg diff --git a/doc/scripting.rst b/doc/scripting.rst index d327c63..1d068b3 100644 --- a/doc/scripting.rst +++ b/doc/scripting.rst @@ -19,9 +19,9 @@ X2CScope class 1. Import the X2Cscope class: - .. code-block:: python +.. code-block:: python - from pyx2scope import X2CScope + from pyx2scope import X2CScope X2CScope class needs one parameter to be instantiated: @@ -32,22 +32,29 @@ and TCP/IP are coming in near future. For serial, the only parameter needed is t default baud rate is set to **115200**. If there's a need to change the baud rate, include the baud_rate parameter with your preferred baud rate. -2. Instantiate X2CScope with the port: +2. Instantiate X2CScope with the serial port number: - .. code-block:: python +.. code-block:: python - x2c_scope = X2CScope(port="COM16") + x2c_scope = X2CScope(port="COM16") -Import variables +Load variables ---------------- -X2Cscope needs the location of the variables inside the controller firmware. The list of variables can be loaded from multiple sources: .elf file, pickle binary and YML text file. +X2Cscope needs to know which variables are currently available on the firmware. +The list of variables can be loaded from multiple file formats: + +- Executable and Linkable Format (ELF, .elf, binary) +- Pickle (PKL, .pkl, binary) +- Yaml (YML, .yml, text) See more details at :ref:`Import and Export variables ` section. +The ELF file is one artifact generated during code compilation. To load the variables, Execute +the code below: - .. code-block:: python +.. code-block:: python - x2c_scope.import_variables(r"..\..\tests\data\qspin_foc_same54.elf") + x2c_scope.import_variables(r"..\..\tests\data\qspin_foc_same54.elf") Variable class -------------- @@ -59,9 +66,9 @@ is a string containing the variable name. 3. Create a Variable object for the variable you want to monitor: - .. code-block:: python +.. code-block:: python - variable = x2c_scope.get_variable('variable_name') + variable = x2c_scope.get_variable('variable_name') Replace 'variable_name' with the name of the variable you want to monitor. You can create multiple variable objects as required. To get variables that are underneath a struct, use the "dot" convention: @@ -74,18 +81,18 @@ Reading values 4. Once you have gone through these steps, you can use the method **get_value()** to retrieve the actual value of the variable: - .. code-block:: python +.. code-block:: python - variable.get_value() + variable.get_value() Writing values ^^^^^^^^^^^^^^ 5. To set the value for the respective variable use the method **set_value()**: - .. code-block:: python +.. code-block:: python - variable.set_value(value) + variable.set_value(value) .. _import-and-export-variables: @@ -116,26 +123,26 @@ seaborn, etc. 1. To use the scope functionality, first you need to link a variable as previously explained, and add this variable to the scope by means of the method: **add_scope_channel(variable: Variable)** : - .. code-block:: python +.. code-block:: python - variable1 = x2c_scope.get_variable("variable1") - variable2 = x2c_scope.get_variable("variable2") + variable1 = x2c_scope.get_variable("variable1") + variable2 = x2c_scope.get_variable("variable2") - x2c_scope.add_scope_channel(variable1) - x2c_scope.add_scope_channel(variable2) + x2c_scope.add_scope_channel(variable1) + x2c_scope.add_scope_channel(variable2) 2. To remove a variable from the scope: **remove_scope_channel(variable: Variable)**, to clear all variables and reset the scope use instead: **clear_all_scope_channel()** - .. code-block:: python +.. code-block:: python - x2c_scope.remove_scope_channel(variable2) + x2c_scope.remove_scope_channel(variable2) or - .. code-block:: python +.. code-block:: python - x2c_scope.clear_all_scope_channel() + x2c_scope.clear_all_scope_channel() Up to 8 channels can be added. Each time you add or remove a variable, the number of channels present on the channel are returned. @@ -145,57 +152,78 @@ Getting Data from Scope To get data from scope channel you need to follow this sequence: -* Request data -* Check if data is ready (sampling is done) - * Data is ready? Yes, get the data and handle it. - * Data is not ready? Execute some delay and check again. -* After handling the data, start from the beginning requesting new data. +:: + + +------------------------------------+ + | Add variables to the scope channel | + +------------------------------------+ + | + v + +--------------------------+ + | Request scope data | <---------------+ + +--------------------------+ | + | | + v | + +--------------------------+ | + | Is scope data ready? | | + +--------------------------+ | + / Yes \ No | + v v | + +-----------------+ +--------------------+ | + | Handle the data | | Execute some delay | | + +-----------------+ +--------------------+ | + | | | + +-----------------+------------------+ + Step-by-step you need: 1. Request to X2CScope to collect data for the variables registered on the scope channels. - .. code-block:: python +.. code-block:: python - x2c_scope.request_scope_data() + x2c_scope.request_scope_data() 2. Check if the data is ready: Returns Scope sampling state. Returns: true if sampling has completed, false if it’s yet in progress. - .. code-block:: python +.. code-block:: python - while not x2c_scope.is_scope_data_ready(): - time.sleep(0.1) + while not x2c_scope.is_scope_data_ready(): + time.sleep(0.1) 3. Get the scope data once sampling is completed - .. code-block:: python +.. code-block:: python - data = x2c_scope.get_scope_channel_data() + data = x2c_scope.get_scope_channel_data() A simple loop request example to get only 1 frame of scope data is depicted below: - .. code-block:: python +.. code-block:: python + + # request scope to start sampling data + x2c_scope.request_scope_data() + + # wait while the data is not yet ready for reading + while not x2c_scope.is_scope_data_ready(): + time.sleep(0.1) - # request scope to start sampling data - x2c_scope.request_scope_data() - # wait while the data is not yet ready for reading - while not x2c_scope.is_scope_data_ready(): - time.sleep(0.1) - for channel, data in x2c_scope.get_scope_channel_data().items(): - # Do something with the data. - # channel contains the variable name, data is an array of values + for channel, data in x2c_scope.get_scope_channel_data().items(): + # Do something with the data. + # channel contains the variable name, data is an array of values Triggering ^^^^^^^^^^ -To Set up Trigger, any available variable can be selected, by default works on no trigger configuration. -To set any trigger configuration, you need to pass a TriggerConfig imported from from pyx2cscope.x2cscope +To set up a Trigger, any variable added to the scope channel can be selected. +By default, there is no trigger selected. +To set any trigger configuration, you need to pass a TriggerConfig imported from pyx2cscope.x2cscope - .. code-block:: python +.. code-block:: python - trigger_config = TriggerConfig(Variable, trigger_level: int, trigger_mode: int, trigger_delay: int, trigger_edge: int) - x2cscope.set_scope_trigger(trigger_config) + trigger_config = TriggerConfig(Variable, trigger_level: int, trigger_mode: int, trigger_delay: int, trigger_edge: int) + x2cscope.set_scope_trigger(trigger_config) TriggerConfig needs some parameters like the variable and some trigger values like: diff --git a/pyproject.toml b/pyproject.toml index 78da6f8..e531b81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "pyx2cscope" -version = "0.4.3" +version = "0.4.4" description = "python implementation of X2Cscope" authors = [ "Yash Agarwal", diff --git a/pyx2cscope/__init__.py b/pyx2cscope/__init__.py index 64f3101..b2874bc 100644 --- a/pyx2cscope/__init__.py +++ b/pyx2cscope/__init__.py @@ -1,11 +1,11 @@ """This module contains the pyx2cscope package. -Version: 0.4.3 +Version: 0.4.4 """ import logging -__version__ = "0.4.3" +__version__ = "0.4.4" def set_logger( diff --git a/pyx2cscope/__main__.py b/pyx2cscope/__main__.py index 0a69693..f5a44a3 100644 --- a/pyx2cscope/__main__.py +++ b/pyx2cscope/__main__.py @@ -11,7 +11,8 @@ import argparse import pyx2cscope -from pyx2cscope import gui, utils +from pyx2cscope import utils +from pyx2cscope import gui def parse_arguments(): diff --git a/pyx2cscope/examples/SFR_Example.py b/pyx2cscope/examples/SFR_Example.py index cb32de1..537942a 100644 --- a/pyx2cscope/examples/SFR_Example.py +++ b/pyx2cscope/examples/SFR_Example.py @@ -68,7 +68,7 @@ def example(): """Main function to demonstrate LED state changes using SFR.""" try: # Initialize the variable for the Special Function Register (SFR) controlling the LEDs - variable_info = VariableInfo("my_led", "int", 1, 3702, 0, {}) + variable_info = VariableInfo("my_led", "int", 1, 0, 0, 3702, 0, {}) sfr_led = x2cscope.get_variable_raw(variable_info) # LATE address from data sheet 3702 # Get the initial LED state from the SFR diff --git a/pyx2cscope/examples/get_device_id.py b/pyx2cscope/examples/get_device_id.py index 5b2eee9..557178a 100644 --- a/pyx2cscope/examples/get_device_id.py +++ b/pyx2cscope/examples/get_device_id.py @@ -38,7 +38,15 @@ speed_measured = x2c_scope.get_variable("mcFocI_ModuleData_gds.dOutput.elecSpeed") # or load a variable directly from the memory -speed_measured_info = VariableInfo("speed_measured", "float", 2, 536879832, 0, {}) +# name: str +# type: str +# byte_size: int +# bit_size: int +# bit_offset: int +# address: int +# array_size: int +# valid_values: Dict[str, int] +speed_measured_info = VariableInfo("speed_measured", "float", 2, 0, 0, 536879832, 0, {}) speed_measured_raw = x2c_scope.get_variable_raw(speed_measured_info) # Read the value of the "motor.apiData.velocityMeasured" variable from the target diff --git a/pyx2cscope/examples/pyX2CScope_demo.py b/pyx2cscope/examples/pyX2CScope_demo.py index 59a0def..a56c362 100644 --- a/pyx2cscope/examples/pyX2CScope_demo.py +++ b/pyx2cscope/examples/pyX2CScope_demo.py @@ -13,6 +13,7 @@ from pyx2cscope import set_logger from pyx2cscope.utils import get_com_port, get_elf_file_path from pyx2cscope.x2cscope import X2CScope +from x2cscope import TriggerConfig set_logger( logging.INFO, @@ -45,13 +46,8 @@ # Setting up Trigger, any available variable can be selected. -x2c_scope.set_scope_trigger( - variable3, - trigger_level=500, - trigger_mode=1, - trigger_delay=50, - trigger_edge=1, -) +config = TriggerConfig(variable3, trigger_level=500, trigger_mode=1, trigger_delay=50, trigger_edge=1) +x2c_scope.set_scope_trigger(config) # Request and Acquire Scope Data # This loop requests and receives data from the scope, storing it in a dictionary for later use. diff --git a/pyx2cscope/gui/__init__.py b/pyx2cscope/gui/__init__.py index f626f7c..74d0ed9 100644 --- a/pyx2cscope/gui/__init__.py +++ b/pyx2cscope/gui/__init__.py @@ -1,8 +1,27 @@ -"""This module is for all the different GUI.""" +"""This module serves as the entry point for launching the various graphical user interfaces (GUIs). + +It provides functions to start either the Qt-based desktop GUI or the web-based GUI server. + +Functions +--------- +- execute_qt(*args, **kwargs): Launches the default Qt GUI interface for X2Cscope. +- execute_web(*args, **kwargs): Starts the web server for the web-based GUI. + +Typical usage example: + + from pyx2cscope.gui import execute_qt, execute_web + + # To launch the Qt GUI: + execute_qt() + + # To launch the web GUI: + execute_web() + +""" def execute_qt(*args, **kwargs): - """Execute the default Qt GUI interface. + """Executes the default Qt GUI interface. Args: args: non-keyed arguments for Qt App. @@ -29,7 +48,7 @@ def execute_qt(*args, **kwargs): def execute_web(*args, **kwargs): - """Start the web server. + """Starts the web server. Args: *args: non-keyed arguments diff --git a/pyx2cscope/gui/generic_gui/generic_gui.py b/pyx2cscope/gui/generic_gui/generic_gui.py index f4c70f5..47ef675 100644 --- a/pyx2cscope/gui/generic_gui/generic_gui.py +++ b/pyx2cscope/gui/generic_gui/generic_gui.py @@ -445,9 +445,7 @@ def create_trigger_configuration_group(self): self.trigger_delay_edit = QLineEdit("0") self.trigger_delay_edit.setValidator(self.decimal_validator) - self.scope_sampletime_edit = QLineEdit( - "50" - ) # Default sample time in microseconds + self.scope_sampletime_edit = QLineEdit("50") # Default sample time in microseconds self.scope_sampletime_edit.setValidator(self.decimal_validator) # Total Time @@ -1560,7 +1558,7 @@ def start_sampling(self): # Set the scope sample time from the user input in microseconds scope_sample_time_us = int(self.scope_sampletime_edit.text()) - self.real_sampletime = self.x2cscope.scope_sample_time( + self.real_sampletime = self.x2cscope.get_scope_sample_time( scope_sample_time_us ) logging.debug(f"Real sample time: {self.real_sampletime} µs") # Check this value diff --git a/pyx2cscope/gui/web/app.py b/pyx2cscope/gui/web/app.py index 6b695de..9767807 100644 --- a/pyx2cscope/gui/web/app.py +++ b/pyx2cscope/gui/web/app.py @@ -132,9 +132,7 @@ def main(host="localhost", web_port=5000, new=True, *args, **kwargs): app.add_url_rule("/connect", view_func=connect, methods=["POST"]) app.add_url_rule("/disconnect", view_func=disconnect) app.add_url_rule("/is-connected", view_func=is_connected) - app.add_url_rule( - "/variables", view_func=variables_autocomplete, methods=["POST", "GET"] - ) + app.add_url_rule("/variables", view_func=variables_autocomplete, methods=["POST", "GET"]) app.add_url_rule("/variables/all", get_variables, methods=["POST", "GET"]) log_level = kwargs["log_level"] if "log_level" in kwargs else "ERROR" @@ -150,12 +148,7 @@ def main(host="localhost", web_port=5000, new=True, *args, **kwargs): if new: Timer(1, open_browser, None, {"web_port": web_port}).start() - print( - "Listening at http://" - + ("localhost" if host == "0.0.0.0" else host) - + ":" - + str(web_port) - ) + print("Listening at http://" + ("localhost" if host == "0.0.0.0" else host) + ":" + str(web_port)) if host == "0.0.0.0": print("Server is open for external requests!") app.run(debug=False, host=host, port=web_port) diff --git a/pyx2cscope/gui/web/views/scope_view.py b/pyx2cscope/gui/web/views/scope_view.py index de9c69e..d855d7b 100644 --- a/pyx2cscope/gui/web/views/scope_view.py +++ b/pyx2cscope/gui/web/views/scope_view.py @@ -54,7 +54,7 @@ def get_data(): """ result = [] for data in scope_data: - result.append({f: v.name if f == "variable" else v for f, v in data.items()}) + result.append({f: v.info.name if f == "variable" else v for f, v in data.items()}) return {"data": result} @@ -65,7 +65,7 @@ def add(): """ with get_x2c() as x2c: parameter = request.args.get("param", "") - if not any(_data["variable"].name == parameter for _data in scope_data): + if not any(_data["variable"].info.name == parameter for _data in scope_data): variable = x2c.get_variable(parameter) data = _get_variable(variable) scope_data.append(data) @@ -91,18 +91,18 @@ def remove(): def _set_trigger(data, param, field, value): if field == "trigger": value = float(value) - if data["variable"].name != param: + if data["variable"].info.name != param: data["trigger"] = 0.0 if value == 1.0 else data["trigger"] def _set_fields(data, param, field, value): - if data["variable"].name == param: + if data["variable"].info.name == param: data[field] = value if field == "color" else float(value) def _set_enable(x2c, data, param, field, value): if field == "enable": - if data["variable"].name == param: + if data["variable"].info.name == param: if float(value): x2c.add_scope_channel(data["variable"]) else: @@ -182,8 +182,8 @@ def _get_datasets(x2c): channel_data = x2c.get_scope_channel_data() for channel in scope_data: # if variable is disabled on scope_data, it is not available on channel_data - if channel["variable"].name in channel_data: - variable = channel["variable"].name + if channel["variable"].info.name in channel_data: + variable = channel["variable"].info.name data_line = [ l * channel["gain"] + channel["offset"] for l in channel_data[variable] ] diff --git a/pyx2cscope/gui/web/views/watch_view.py b/pyx2cscope/gui/web/views/watch_view.py index 9dc7e53..47f3c9a 100644 --- a/pyx2cscope/gui/web/views/watch_view.py +++ b/pyx2cscope/gui/web/views/watch_view.py @@ -57,8 +57,8 @@ def get_data(): for _data in watch_data: if _data["live"]: _read_variable(_data) - result.append({f: v.name if f == "variable" else v for f, v in _data.items()}) - return {"data": result} + result.append({f: v.info.name if f == "variable" else v for f, v in _data.items()}) + return {"data": result} def add(): @@ -68,7 +68,7 @@ def add(): """ parameter = request.args.get("param", "") with get_x2c() as x2c: - if not any(_data["variable"].name == parameter for _data in watch_data): + if not any(_data["variable"].info.name == parameter for _data in watch_data): variable = x2c.get_variable(parameter) watch_data.append(_get_variable_as_dict(variable)) return jsonify({"status": "success"}) @@ -81,7 +81,7 @@ def remove(): """ parameter = request.args.get("param", "") for _data in watch_data: - if _data["variable"].name == parameter: + if _data["variable"].info.name == parameter: watch_data.remove(_data) break return jsonify({"status": "success"}) @@ -96,7 +96,7 @@ def update(): field = request.args.get("field", "").lower() value = request.args.get("value", "") for _data in watch_data: - if _data["variable"].name == param: + if _data["variable"].info.name == param: _data[field] = float(value) if field == "value": _data["variable"].set_value(_data[field]) diff --git a/pyx2cscope/parser/elf16_parser.py b/pyx2cscope/parser/elf16_parser.py index fabbba5..f79156d 100644 --- a/pyx2cscope/parser/elf16_parser.py +++ b/pyx2cscope/parser/elf16_parser.py @@ -17,7 +17,8 @@ import warnings from shutil import which -from pyx2cscope.parser.elf_parser import ElfParser, VariableInfo +from pyx2cscope.parser.elf_parser import ElfParser +from pyx2cscope.variable.variable import VariableInfo # Constants for magic values ADDRESS_CHECK_LOWER_LIMIT = 2 diff --git a/pyx2cscope/parser/generic_parser.py b/pyx2cscope/parser/generic_parser.py index 342084f..b2e98a4 100644 --- a/pyx2cscope/parser/generic_parser.py +++ b/pyx2cscope/parser/generic_parser.py @@ -8,10 +8,12 @@ from elftools.elf.elffile import ELFFile from elftools.elf.sections import SymbolTableSection -from pyx2cscope.parser.elf_parser import ElfParser, VariableInfo +from pyx2cscope.parser.elf_parser import ElfParser from elftools.construct.lib import ListContainer from elftools.dwarf.dwarf_expr import DWARFExprParser +from pyx2cscope.variable.variable import VariableInfo + class GenericParser(ElfParser): """Class for parsing ELF files compatible with 32-bit architectures.""" @@ -184,7 +186,7 @@ def _find_actual_declaration(self, die_variable): die_variable = self.dwarf_info.get_DIE_from_refaddr(spec_ref_addr) return die_variable - def _get_member_offset(self, die) -> [int, int, int]: + def _get_member_offset(self, die) -> tuple[int | None, int, int]: """Extracts the offset for a structure member. Args: diff --git a/pyx2cscope/variable/variable.py b/pyx2cscope/variable/variable.py index c1c3c80..5c6b460 100644 --- a/pyx2cscope/variable/variable.py +++ b/pyx2cscope/variable/variable.py @@ -57,18 +57,12 @@ class VariableInfo: class Variable: """Represents a variable in the MCU data memory.""" - def __init__( - self, l_net: LNet, info: VariableInfo - ) -> None: + def __init__(self, l_net: LNet, info: VariableInfo) -> None: """Initialize the Variable object. - On var_info, the minimal information needed are name, address, and array_size. This is valid - for most of the variables. Enums will need enum_list, and unions having elements which are smaller - than a byte. - Args: l_net (LNet): LNet protocol that handles the communication with the target device. - info (VariableInfo): Address of the variable in the MCU memory. + info (VariableInfo): Information of the variable in the MCU memory. """ if type(self) == Variable: # protect super class to be initiated directly raise Exception(" must be subclassed.") @@ -952,13 +946,16 @@ def is_signed(self) -> bool: return False def get_width(self) -> int: - """Get the width of the 16-bit enum. + """Get the width of the enum. + + The enum may have variable byte size, depending on the mcu architecture and the + amount of elements contained under each specific enumeration. The size is obtained + during parsing and is described under byte_size. Returns: - int: Width of the variable, which is 2. + int: Width of the variable in bytes. """ - #TODO depends on architecture and enum count of elements - return self.l_net.device_info.uc_width + return self.info.byte_size def _set_value(self, value: int): diff --git a/pyx2cscope/x2cscope.py b/pyx2cscope/x2cscope.py index 0c4dbe8..220705f 100644 --- a/pyx2cscope/x2cscope.py +++ b/pyx2cscope/x2cscope.py @@ -40,8 +40,8 @@ def get_variable_as_scope_channel(variable: Variable) -> ScopeChannel: ScopeChannel: A ScopeChannel object representing the given variable. """ return ScopeChannel( - name=variable.name, - source_location=variable.address, + name=variable.info.name, + source_location=variable.info.address, data_type_size=variable.get_width(), source_type=0, is_integer=variable.is_integer(), @@ -83,7 +83,7 @@ class X2CScope: uc_width (int): the processor architecture 2: 16 bit, 4: 32 bit. """ - def __init__(self, elf_file: str = None, interface: InterfaceABC = None, **kwargs): + def __init__(self, elf_file: str = None, interface: InterfaceType = None, **kwargs): """Initialize the X2CScope instance. Args: @@ -179,7 +179,7 @@ def add_scope_channel(self, variable: Variable, trigger: bool = False) -> int: int: The ID of the added scope channel. """ scope_channel = get_variable_as_scope_channel(variable) - self.convert_list[variable.name] = variable.bytes_to_value + self.convert_list[variable.info.name] = variable.bytes_to_value return self.scope_setup.add_channel(scope_channel, trigger) def clear_all_scope_channel(self): @@ -204,9 +204,9 @@ def remove_scope_channel(self, variable: Variable): Returns: The result of the channel removal operation. """ - if variable.name in self.convert_list: - self.convert_list.pop(variable.name) - return self.scope_setup.remove_channel(variable.name) + if variable.info.name in self.convert_list: + self.convert_list.pop(variable.info.name) + return self.scope_setup.remove_channel(variable.info.name) def get_scope_channel_list(self) -> Dict[str, ScopeChannel]: """Get a list of all scope channels. @@ -240,14 +240,15 @@ def clear_trigger(self): self.scope_setup.reset_trigger() def set_sample_time(self, sample_time: int): - """Define a pre-scaler for sampling mode. + """Define the resolution how the samples will be buffered at the internal buffer. This can be used to extend total sampling time at the cost of resolution. - 0 = every sample, 1 = every 2nd sample, 2 = every 3rd sample … + 1 = every sample, 2 = every 2nd sample, 3 = every 3rd sample … Args: sample_time (int): The sample time factor. """ + sample_time = 1 if sample_time < 1 else sample_time self.scope_setup.set_sample_time_factor(sample_time) def set_scope_state(self, scope_state: int): @@ -411,9 +412,7 @@ def _filter_channels( channels[channel].extend(rest) return channels - def get_scope_channel_data( - self, valid_data: bool = True - ) -> Dict[str, List[Number]]: + def get_scope_channel_data(self, valid_data: bool = True) -> Dict[str, List[Number]]: """Get the sorted and optionally filtered scope channel data. Args: @@ -429,13 +428,17 @@ def get_scope_channel_data( return self._filter_channels(channels) if valid_data else channels return {} - def scope_sample_time( - self, time_microseconds: float - ) -> float: # TODO testing and implementing the time axis. + def get_scope_sample_time(self, time: float) -> float: """Evaluate the scope sample time based on user-provided time value. + X2Cscope has an internal buffer which is filled with data at a specific rate. + The argument time relates to the sampling rate of each sample. The total scope + channel time depends on the size of the internal buffer, the sampling rate, and + the time factor (resolution) set when starting the scope channel. See also + set_sample_time + Args: - time_microseconds (float): The time value in microseconds for evaluating one scope sample. + time (float): The time value in microseconds of one sample. Returns: float: The real-time duration for the scope functionality in milliseconds. @@ -452,7 +455,7 @@ def scope_sample_time( samples_in_buffer = buffer_size // dataset_size # Calculate the total time duration for the samples in the buffer - total_time_microseconds = time_microseconds * samples_in_buffer + total_time_microseconds = time * samples_in_buffer # Convert the total time to milliseconds total_time_milliseconds = total_time_microseconds / 1000 @@ -460,11 +463,12 @@ def scope_sample_time( logging.info( f"Total time for the scope functionality: {total_time_milliseconds} ms" ) - return self.scope_setup.sample_time_factor * total_time_milliseconds * 2 + return self.scope_setup.sample_time_factor * total_time_milliseconds def get_device_info(self): """Returns the device information as a dictionary.""" device_info = self.variable_factory.device_info + uc_width_value = "8-bit" if device_info.uc_width == UC_WIDTH_16BIT: uc_width_value = "16-bit" elif device_info.uc_width == UC_WIDTH_32BIT: