Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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]+' pyx2cscope/__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
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "pyx2cscope"
version = "0.4.4"
version = "0.5.0"
description = "python implementation of X2Cscope"
authors = [
"Yash Agarwal",
Expand Down 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.1"
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"]


11 changes: 9 additions & 2 deletions pyx2cscope/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
"""This module contains the pyx2cscope package.

Version: 0.4.4
Version: 0.5.0
"""

# Apply eventlet monkey patch before any other imports if web interface is requested
import sys

if "-w" in sys.argv or "--web" in sys.argv:
import eventlet
eventlet.monkey_patch()

import logging

__version__ = "0.4.4"
__version__ = "0.5.0"


def set_logger(
Expand Down
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)
67 changes: 67 additions & 0 deletions pyx2cscope/examples/enum_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""PyX2CScope enumerator example reference.

This example get a enum variable, get its enum values and set a different value either as a number or as a enum.

following enum is defined as a static variable:

typedef enum {
A,
B,
C,
D
} LETTERS;

static LETTERS letter = A;
"""



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

# X2C Scope Set up
# The X2C Scope is a tool for real-time data acquisition from a microcontroller.
# Here, we specify the COM port and the path to the ELF file of the microcontroller project.
elf_file = get_elf_file_path()
x2c_scope = X2CScope(port=get_com_port(), elf_file=elf_file)

# Scope Configuration Here, we set up the variables we want to monitor using the X2C Scope. Each variable corresponds
# to a specific data point in the microcontroller.
letter = x2c_scope.get_variable("letter")

# we can check is this variable is an enum
# True
print(letter.is_enum())

# as this variable is an enum, we can get the integer value of the enum
# {'A': 0, 'B': 1, 'C': 2, 'D': 3}
print(letter.get_enum_list())

# we can retrieve its value
# 0
print(letter.get_value())

# we can set the integer value of the enum as by other variable types
letter.set_value(3)
# output is 3
print(letter.get_value())

# we can define a dictionary with the enum values
LETTERS = letter.get_enum_list()
# and use the string values in this manner to set integer values
letter.set_value(LETTERS["B"])
# output is 1
print(letter.get_value())

# or we can call the specific set_enum_value method
letter.set_enum_value("C")
# output is 2
print(letter.get_value())

# we can get the string representation of the enum variable
print(letter.get_enum_value())
# output is 'C'




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
Loading