Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ba6db6d
fix index_sv.html name pattern
Nov 19, 2025
a52cd51
convert to bootstrap 5
Nov 19, 2025
a7b1280
update mchplnet pointer [email protected]
Nov 19, 2025
b66f0a5
fixed bug removing variables from scope view web
Nov 19, 2025
e78ad26
visual fix on mobile view
Nov 19, 2025
79aa4a0
fixed overlay of dropdown and header of table
Nov 19, 2025
58499ab
variable hide on scope_view.js
Nov 19, 2025
a09decb
fixing demos
Nov 19, 2025
6819bcf
websockets for watch-view and scope-view
Nov 19, 2025
34e086f
pointer to mchplnet commit
Nov 19, 2025
8581b12
bug fix - trigger_level
Nov 19, 2025
1e5edf1
removing prints
Nov 19, 2025
6170d4c
fix doc misspelling
Nov 19, 2025
b580267
fixing example readability
Nov 19, 2025
6bff49c
when adding a variable on watch-view, read the value before
Nov 19, 2025
b507361
fixed labels on x-axis on webview
Nov 19, 2025
4ad5739
fix views when connect/disconnect
Nov 19, 2025
7b26d75
default log level for x2cscope is error
Nov 19, 2025
4a72130
enhancing serial_stub.py for get_ram and put_ram
Nov 19, 2025
3253f6d
included test for thread safety
Nov 19, 2025
8e5637e
fixing interface on mchplnet
Nov 19, 2025
c6faf18
enhancing examples
Nov 19, 2025
7d1bae3
enhancing existing tests
Nov 19, 2025
44cc91e
enhancing github actions documentation.yml
Nov 19, 2025
8f05b1e
fixing to default snake_case on mchplnet
Nov 19, 2025
6d84d94
update mchplnet pointer
Nov 19, 2025
9f29962
fix docstrings
Nov 19, 2025
bc13d26
fix imports format
Nov 19, 2025
5b7bbb8
fix docstring on tests
Nov 19, 2025
9fc2b9e
removing elf16 parser
Nov 19, 2025
1a8fd5d
include linter ignore.
Nov 19, 2025
5fd8499
removing debug prints
Nov 19, 2025
8aed6d2
including version check and auto-update of version on __init__.py on …
Nov 19, 2025
57da1bc
update mchplnet to version 0.3.0
Nov 19, 2025
715f1ea
fix mchplnet dependency
Nov 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,41 @@ jobs:
run: |
sphinx-build -M html doc build --keep-going

version-check:
runs-on: ubuntu-latest
if: github.base_ref == 'main'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Check version consistency
run: |
# Get version from pyproject.toml
PYPROJECT_VERSION=$(grep -oP 'version\s*=\s*"\K[^"]+' pyproject.toml)
# Get version from __init__.py
INIT_VERSION=$(grep -oP '__version__\s*=\s*["\x27]\K[^"\x27]+' mchplnet/__init__.py)

if [ "$PYPROJECT_VERSION" != "$INIT_VERSION" ]; then
echo "❌ Version mismatch between pyproject.toml ($PYPROJECT_VERSION) and __init__.py ($INIT_VERSION)"
echo "Run 'python scripts/update_version.py' to synchronize versions"
exit 1
fi
echo "✅ Version consistency verified: $PYPROJECT_VERSION"

- name: Check version has been updated
if: github.base_ref == 'main'
run: |
# Get PR version
PR_VERSION=$(grep -oP 'version\s*=\s*"\K[^"]+' pyproject.toml)

# Get main branch version
git fetch origin main:main
MAIN_VERSION=$(git show main:pyproject.toml | grep -oP 'version\s*=\s*"\K[^"]+')

if [ "$PR_VERSION" == "$MAIN_VERSION" ]; then
echo "❌ ERROR: Version in PR ($PR_VERSION) is the same as in main branch ($MAIN_VERSION)"
echo "Please update the version in pyproject.toml"
exit 1
fi
echo "✅ Version check passed: $PR_VERSION (PR) vs $MAIN_VERSION (main)"
10 changes: 10 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
repos:
- repo: local
hooks:
- id: update-version
name: Update version in __init__.py
entry: python scripts/update_version.py
language: python
files: ^pyproject.toml$
types: [toml]
always_run: true
2 changes: 1 addition & 1 deletion doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
("py:class", "mchplnet.lnet.LNet"),
("py:class", "mchplnet.services.scope.ScopeChannel"),
("py:class", "mchplnet.interfaces.factory.InterfaceType"),
("py:class", "mchplnet.interfaces.abstract_interface.InterfaceABC"),
("py:class", "mchplnet.interfaces.abstract_interface.Interface"),
]

intersphinx_mapping = {
Expand Down
2 changes: 1 addition & 1 deletion doc/install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Create a virtual environment and install pyx2cscope using the following commands

It is highly recommended to install python libraries underneath a virtual environment.

Nevertheless, to install at system level, we advise to install it on user area. (Global insallation may not work.)
Nevertheless, to install at system level, we advise to install it on user area. (Global installation may not work.)
Execute the following lines:

.. code-block:: python
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ numpy = "^1.26.0"
matplotlib = "^3.7.2"
PyQt5 = "^5.15.9"
pyqtgraph= "^0.13.7"
mchplnet = "0.2.2"
mchplnet = "0.3.0"
flask = "^3.0.3"


Expand All @@ -49,6 +49,6 @@ lint.select = ["D"]
# PL - pylint
lint.extend-select = ["I", "N", "F", "PL", "D"]
exclude = ["mchplnet/*"]
lint.ignore = ["I001", "PLW0603", "PLW0602", "PLR0913"] #TODO fix the import issue. no errors on local machine and errors on github action.
lint.ignore = ["PLR0913"]


3 changes: 1 addition & 2 deletions pyx2cscope/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
import argparse

import pyx2cscope
from pyx2cscope import utils
from pyx2cscope import gui
from pyx2cscope import gui, utils


def parse_arguments():
Expand Down
3 changes: 2 additions & 1 deletion pyx2cscope/examples/SFR_Example.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import logging
import time

from variable.variable import VariableInfo

from pyx2cscope.utils import get_com_port, get_elf_file_path
from pyx2cscope.x2cscope import X2CScope
from variable.variable import VariableInfo

# Configuration for serial port communication
serial_port = get_com_port() # Select COM port
Expand Down
8 changes: 6 additions & 2 deletions pyx2cscope/examples/demo.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""Demo scripting for user to get started."""
import time

from pyx2cscope.x2cscope import X2CScope

elf_file =r"path to your elf file.elf"

x2cscope = X2CScope(port="COM39", elf_file=elf_file)
x2cscope = X2CScope(port="COM4", elf_file=elf_file)

phase_current = x2cscope.get_variable("motor.iabc.a")
phase_voltage = x2cscope.get_variable("motor.vabc.a")
Expand All @@ -15,4 +17,6 @@

while True:
if x2cscope.is_scope_data_ready():
print(x2cscope.get_scope_channel_data())
print(x2cscope.get_scope_channel_data())
x2cscope.request_scope_data()
time.sleep(0.1)
136 changes: 113 additions & 23 deletions pyx2cscope/examples/exampleMCAF.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,124 @@
"""exampleMCAF.py.

this example is a basic example to retrieve the value of a variable from motorBench auto-generated code.
This example demonstrate a HiL (Hardware-in-the-Loop) script using motorBench (MCAF).
The script assumes the control of MCAF disabling the hardware UI, stop the motor if
it is running. Then it sets the velocity reference to 500 RPM and starts the motor.
After that it measures the time to reach the speed, waits 2 seconds and calculates
the RMS current using the scope functionality. After that it increases the velocity
reference to 1500 RPM and measures the time to reach the speed. Waits 2 seconds and
calculates the RMS current at this speed. The script stops the motor and gives the
control back to hardware UI.
"""

import time

import numpy as np

from pyx2cscope.utils import get_com_port, get_elf_file_path
from pyx2cscope.x2cscope import X2CScope

# Configuration for serial port communication
serial_port = get_com_port() # Get the COM port from the utility function
baud_rate = 115200 # Set baud rate for serial communication

# Specify the path to the ELF file
elf_file = get_elf_file_path() # Get the path to the ELF file from the utility function

# Initialize the X2CScope with the specified serial port and baud rate
# The default baud rate is 115200
x2c_scope = X2CScope(port=serial_port, baud_rate=baud_rate, elf_file=elf_file)

# Retrieve specific variables from the MCU
speed_reference = x2c_scope.get_variable(
"motor.apiData.velocityReference"
) # Get the speed reference variable
speed_measured = x2c_scope.get_variable(
"motor.apiData.velocityMeasured"
) # Get the measured speed variable

# Attempt to read the value of the 'speedReference' variable and log the result
try:
# Read the value of the speed reference variable
speed_reference_value = speed_reference.get_value()
print(f"Speed Reference Value: {speed_reference_value}")
except Exception as e:
print(f"Error reading speed reference value: {e}")
# Initialize the X2CScope with the specified serial port and ELF file
x2c_scope = X2CScope(port=serial_port, elf_file=elf_file)

# Set hardware UI enabled to 0
# doing this we enable external control of the motor
hardware_ui_enabled = x2c_scope.get_variable("app.hardwareUiEnabled")
hardware_ui_enabled.set_value(0)

# Ensure the motor is stopped
stop_motor_request = x2c_scope.get_variable("motor.apiData.stopMotorRequest")
stop_motor_request.set_value(1)

# define target speeds
# Speed threshold in RPM (5000 = 500.0 RPM)
TARGET_SPEED_1_RPM = 5000
TARGET_SPEED_2_RPM = 15000

# Set the speed to TARGET_SPEED_1_RPM
velocity_reference = x2c_scope.get_variable("motor.apiData.velocityReference")
velocity_reference.set_value(TARGET_SPEED_1_RPM)

# Start the motor
run_motor_request = x2c_scope.get_variable("motor.apiData.runMotorRequest")
run_motor_request.set_value(1)

# Measure the time to reach the speed
start_time = time.time()
while True:
speed_measured = x2c_scope.get_variable("motor.apiData.velocityMeasured")
if speed_measured.get_value() >= TARGET_SPEED_1_RPM:
break
end_time = time.time()
time_to_reach_speed_1 = end_time - start_time
print(f"Time to reach {TARGET_SPEED_1_RPM/10} RPM: {time_to_reach_speed_1:.2f} seconds")

# Wait for 2 seconds
time.sleep(2)

# Add current variables to the scope
phase_current_a = x2c_scope.get_variable("motor.iabc.a")
phase_current_b = x2c_scope.get_variable("motor.iabc.b")
x2c_scope.add_scope_channel(phase_current_a)
x2c_scope.add_scope_channel(phase_current_b)

# Request scope data
x2c_scope.request_scope_data()

# Wait until the scope data is ready
while not x2c_scope.is_scope_data_ready():
time.sleep(0.1)

# Get the scope channel data
scope_data = x2c_scope.get_scope_channel_data()
current_a = scope_data["motor.iabc.a"]
current_b = scope_data["motor.iabc.b"]

# Calculate the RMS current
rms_current_a = np.sqrt(np.mean(np.square(current_a))) * 0.1 # Convert to Amps
rms_current_b = np.sqrt(np.mean(np.square(current_b))) * 0.1 # Convert to Amps
average_current_speed_1 = (rms_current_a + rms_current_b) / 2
print(f"Average current at {TARGET_SPEED_1_RPM/10} RPM: {average_current_speed_1:.2f} A")

# Increase the speed to TARGET_SPEED_2_RPM
velocity_reference.set_value(TARGET_SPEED_2_RPM)

# Measure the time to reach the new speed
start_time = time.time()
while True:
speed_measured = x2c_scope.get_variable("motor.apiData.velocityMeasured")
if speed_measured.get_value() >= TARGET_SPEED_2_RPM:
break
end_time = time.time()
time_to_reach_speed_2 = end_time - start_time
print(f"Time to reach {TARGET_SPEED_2_RPM/10} RPM: {time_to_reach_speed_2:.2f} seconds")

# Wait for 2 seconds
time.sleep(2)

# Request scope data again
x2c_scope.request_scope_data()

# Wait until the scope data is ready
while not x2c_scope.is_scope_data_ready():
time.sleep(0.1)

# Get the scope channel data again
scope_data = x2c_scope.get_scope_channel_data()
current_a = scope_data["motor.iabc.a"]
current_b = scope_data["motor.iabc.b"]

# Calculate the RMS current for the new speed
rms_current_a = np.sqrt(np.mean(np.square(current_a))) * 0.1 # Convert to Amps
rms_current_b = np.sqrt(np.mean(np.square(current_b))) * 0.1 # Convert to Amps
average_current_speed_2 = (rms_current_a + rms_current_b) / 2
print(f"Average current at {TARGET_SPEED_2_RPM/10} RPM: {average_current_speed_2:.2f} A")

# Stop the motor
stop_motor_request.set_value(1)

# Set hardware UI enabled to 1. Doing this we give the control back to the hardware UI.
hardware_ui_enabled.set_value(1)
5 changes: 3 additions & 2 deletions pyx2cscope/examples/export_import_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
"""
import os

from pyx2cscope.x2cscope import X2CScope
from pyx2cscope.utils import get_com_port
from pyx2cscope.variable.variable_factory import FileType
from pyx2cscope.x2cscope import X2CScope

# initialize the X2CScope class with serial port, by default baud rate is 115200
com_port = "COM32"
com_port = get_com_port() # Get the COM port from the utility function
x2c_scope = X2CScope(port=com_port)
# instead of loading directly the elf file, we can import it after instantiating the X2CScope class
x2c_scope.import_variables(r"..\..\tests\data\qspin_foc_same54.elf")
Expand Down
3 changes: 2 additions & 1 deletion pyx2cscope/examples/get_device_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
device's memory.
"""

from pyx2cscope.x2cscope import X2CScope
from variable.variable import VariableInfo

from pyx2cscope.x2cscope import X2CScope

# initialize the X2CScope class with serial port, by default baud rate is 115200
x2c_scope = X2CScope(port="COM32")

Expand Down
52 changes: 22 additions & 30 deletions pyx2cscope/examples/live_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,46 +38,38 @@
fig, ax = plt.subplots()

# Main loop
sample_count = 0
max_sample = 100 # Increase the number of samples if needed
nr_of_samples = 10 # Increase the number of samples if needed
data_storage = {}

# request scope to start sampling data
x2c_scope.request_scope_data()

while sample_count < max_sample:
try:
if x2c_scope.is_scope_data_ready():
sample_count += 1
logging.info("Scope data is ready.")
while nr_of_samples > 0:
# check if scope data is already available
if x2c_scope.is_scope_data_ready():
logging.info("Scope data is ready.")

data_storage = {}
for channel, data in x2c_scope.get_scope_channel_data(
valid_data=False
).items():
data_storage[channel] = data
for channel, data in x2c_scope.get_scope_channel_data().items():
data_storage[channel] = data

ax.clear()
for channel, data in data_storage.items():
# Assuming data is sampled at 1 kHz, adjust as needed
time_values = [i * 0.001 for i in range(len(data))] # milliseconds
# time_values = [i * 0.000001 for i in range(len(data))] # microseconds
ax.plot(time_values, data, label=f"Channel {channel}")
ax.clear()
for channel, data in data_storage.items():
# Assuming data is sampled at 1 kHz, adjust as needed
time_values = [i * 0.001 for i in range(len(data))] # milliseconds
# time_values = [i * 0.000001 for i in range(len(data))] # microseconds
ax.plot(time_values, data, label=f"Channel {channel}")

ax.set_xlabel("Time (ms)") # Change axis label accordingly
ax.set_ylabel("Value")
ax.set_title("Live Plot of Byte Data")
ax.legend()
ax.set_xlabel("Time (ms)") # Change axis label accordingly
ax.set_ylabel("Value")
ax.set_title("Live Plot of Byte Data")
ax.legend()

plt.pause(0.001) # Add a short pause to update the plot
plt.pause(0.001) # Add a short pause to update the plot

if sample_count >= max_sample:
break
x2c_scope.request_scope_data()

except Exception as e:
logging.error(f"Error in main loop: {str(e)}")
break
nr_of_samples -= 1
x2c_scope.request_scope_data()

# wait for 100 ms before calling is_scope_data_ready() again
time.sleep(0.1)

plt.ioff() # Turn off interactive mode after the loop
Expand Down
Loading