Skip to content

Commit af98492

Browse files
authored
Merge branch 'master' into feature-proposed-data-type-fix
2 parents e669d6b + 7ddb19b commit af98492

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+3397
-1179
lines changed

.github/workflows/pr-linters.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Run PR linters
2+
3+
on:
4+
pull_request:
5+
workflow_dispatch:
6+
7+
permissions:
8+
contents: read
9+
pull-requests: read
10+
11+
jobs:
12+
13+
mypy:
14+
name: Run mypy static type checker (optional)
15+
runs-on: ubuntu-latest
16+
continue-on-error: true
17+
steps:
18+
- uses: actions/checkout@v4
19+
- uses: actions/setup-python@v5
20+
with:
21+
python-version: 3.12
22+
cache: pip
23+
cache-dependency-path: |
24+
'pyproject.toml'
25+
'requirements-dev.txt'
26+
- run: pip install -r requirements-dev.txt -e .
27+
- name: Run mypy and report
28+
run: mypy --config-file pyproject.toml .

.github/workflows/pythonpackage.yml

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@ name: Python package
55

66
on:
77
push:
8-
branches: [ "master" ]
8+
branches:
9+
- 'master'
10+
paths-ignore:
11+
- 'README.rst'
12+
- 'LICENSE.txt'
913
pull_request:
10-
branches: [ "master" ]
14+
branches:
15+
- 'master'
16+
paths-ignore:
17+
- 'README.rst'
18+
- 'LICENSE.txt'
1119

1220
jobs:
1321
build:
@@ -17,18 +25,39 @@ jobs:
1725
fail-fast: false
1826
matrix:
1927
python-version: ['3.x']
28+
features: ['', '[db_export]']
2029

2130
steps:
22-
- uses: actions/checkout@v3
31+
- uses: actions/checkout@v4
2332
- name: Set up Python ${{ matrix.python-version }}
24-
uses: actions/setup-python@v3
33+
uses: actions/setup-python@v5
2534
with:
2635
python-version: ${{ matrix.python-version }}
36+
cache: 'pip'
37+
cache-dependency-path: |
38+
'pyproject.toml'
39+
'requirements-dev.txt'
2740
- name: Install dependencies
28-
run: |
29-
python -m pip install --upgrade pip
30-
pip install pytest
31-
pip install -e .
41+
run: python3 -m pip install -e '.${{ matrix.features }}' -r requirements-dev.txt
3242
- name: Test with pytest
33-
run: |
34-
pytest -v
43+
run: pytest -v --cov=canopen --cov-report=xml --cov-branch
44+
- name: Upload coverage reports to Codecov
45+
uses: codecov/codecov-action@v5
46+
with:
47+
token: ${{ secrets.CODECOV_TOKEN }}
48+
49+
docs:
50+
runs-on: ubuntu-latest
51+
steps:
52+
- uses: actions/checkout@v4
53+
- uses: actions/setup-python@v5
54+
with:
55+
python-version: 3.12
56+
cache: 'pip'
57+
cache-dependency-path: |
58+
'pyproject.toml'
59+
'doc/requirements.txt'
60+
- name: Install dependencies
61+
run: python3 -m pip install -r doc/requirements.txt -e .
62+
- name: Build docs
63+
run: make -C doc html

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ coverage.xml
5757
*.log
5858

5959
# Sphinx documentation
60-
docs/_build/
60+
doc/_build/
6161

6262
# PyBuilder
6363
target/

README.rst

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ The aim of the project is to support the most common parts of the CiA 301
66
standard in a simple Pythonic interface. It is mainly targeted for testing and
77
automation tasks rather than a standard compliant master implementation.
88

9-
The library supports Python 3.6+.
9+
The library supports Python 3.8 or newer.
1010

1111

1212
Features
@@ -36,26 +36,30 @@ Incomplete support for creating slave nodes also exists.
3636
Installation
3737
------------
3838

39-
Install from PyPI_ using pip::
39+
Install from PyPI_ using :program:`pip`::
4040

4141
$ pip install canopen
4242

43-
Install from latest master on GitHub::
43+
Install from latest ``master`` on GitHub::
4444

45-
$ pip install https://github.com/christiansandberg/canopen/archive/master.zip
45+
$ pip install https://github.com/canopen-python/canopen/archive/master.zip
4646

4747
If you want to be able to change the code while using it, clone it then install
4848
it in `develop mode`_::
4949

50-
$ git clone https://github.com/christiansandberg/canopen.git
50+
$ git clone https://github.com/canopen-python/canopen.git
5151
$ cd canopen
5252
$ pip install -e .
5353

5454
Unit tests can be run using the pytest_ framework::
5555

56-
$ pip install pytest
56+
$ pip install -r requirements-dev.txt
5757
$ pytest -v
5858

59+
You can also use :mod:`unittest` standard library module::
60+
61+
$ python3 -m unittest discover test -v
62+
5963
Documentation
6064
-------------
6165

@@ -65,7 +69,8 @@ http://canopen.readthedocs.io/en/latest/
6569

6670
It can also be generated from a local clone using Sphinx_::
6771

68-
$ python setup.py build_sphinx
72+
$ pip install -r doc/requirements.txt
73+
$ make -C doc html
6974

7075

7176
Hardware support
@@ -107,12 +112,12 @@ The :code:`n` is the PDO index (normally 1 to 4). The second form of access is f
107112
# Arguments are passed to python-can's can.Bus() constructor
108113
# (see https://python-can.readthedocs.io/en/latest/bus.html).
109114
network.connect()
110-
# network.connect(bustype='socketcan', channel='can0')
111-
# network.connect(bustype='kvaser', channel=0, bitrate=250000)
112-
# network.connect(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000)
113-
# network.connect(bustype='ixxat', channel=0, bitrate=250000)
114-
# network.connect(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000)
115-
# network.connect(bustype='nican', channel='CAN0', bitrate=250000)
115+
# network.connect(interface='socketcan', channel='can0')
116+
# network.connect(interface='kvaser', channel=0, bitrate=250000)
117+
# network.connect(interface='pcan', channel='PCAN_USBBUS1', bitrate=250000)
118+
# network.connect(interface='ixxat', channel=0, bitrate=250000)
119+
# network.connect(interface='vector', app_name='CANalyzer', channel=0, bitrate=250000)
120+
# network.connect(interface='nican', channel='CAN0', bitrate=250000)
116121
117122
# Read a variable using SDO
118123
device_name = node.sdo['Manufacturer device name'].raw

canopen/__init__.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
11
from canopen.network import Network, NodeScanner
2-
from canopen.node import RemoteNode, LocalNode
3-
from canopen.sdo import SdoCommunicationError, SdoAbortedError
4-
from canopen.objectdictionary import import_od, export_od, ObjectDictionary, ObjectDictionaryError
2+
from canopen.node import LocalNode, RemoteNode
3+
from canopen.objectdictionary import (
4+
ObjectDictionary,
5+
ObjectDictionaryError,
6+
export_od,
7+
import_od,
8+
)
59
from canopen.profiles.p402 import BaseNode402
10+
from canopen.sdo import SdoAbortedError, SdoCommunicationError
11+
612
try:
713
from canopen._version import version as __version__
814
except ImportError:
915
# package is not installed
1016
__version__ = "unknown"
1117

12-
Node = RemoteNode
13-
18+
__all__ = [
19+
"Network",
20+
"NodeScanner",
21+
"RemoteNode",
22+
"LocalNode",
23+
"SdoCommunicationError",
24+
"SdoAbortedError",
25+
"import_od",
26+
"export_od",
27+
"ObjectDictionary",
28+
"ObjectDictionaryError",
29+
"BaseNode402",
30+
]
1431
__pypi_url__ = "https://pypi.org/project/canopen/"
32+
33+
Node = RemoteNode

canopen/emcy.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import struct
21
import logging
2+
import struct
33
import threading
44
import time
55
from typing import Callable, List, Optional
66

7+
import canopen.network
8+
9+
710
# Error code, error register, vendor specific data
811
EMCY_STRUCT = struct.Struct("<HB5s")
912

@@ -82,7 +85,7 @@ def wait(
8285
class EmcyProducer:
8386

8487
def __init__(self, cob_id: int):
85-
self.network = None
88+
self.network: canopen.network.Network = canopen.network._UNINITIALIZED_NETWORK
8689
self.cob_id = cob_id
8790

8891
def send(self, code: int, register: int = 0, data: bytes = b""):
@@ -130,7 +133,7 @@ def get_desc(self) -> str:
130133
return ""
131134

132135
def __str__(self):
133-
text = "Code 0x{:04X}".format(self.code)
136+
text = f"Code 0x{self.code:04X}"
134137
description = self.get_desc()
135138
if description:
136139
text = text + ", " + description

canopen/lss.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import logging
2-
import time
3-
import struct
42
import queue
3+
import struct
4+
import time
5+
6+
import canopen.network
7+
58

69
logger = logging.getLogger(__name__)
710

@@ -78,8 +81,8 @@ class LssMaster:
7881
#: Max time in seconds to wait for response from server
7982
RESPONSE_TIMEOUT = 0.5
8083

81-
def __init__(self):
82-
self.network = None
84+
def __init__(self) -> None:
85+
self.network: canopen.network.Network = canopen.network._UNINITIALIZED_NETWORK
8386
self._node_id = 0
8487
self._data = None
8588
self.responses = queue.Queue()
@@ -239,12 +242,12 @@ def send_identify_non_configured_remote_slave(self):
239242
self.__send_command(message)
240243

241244
def fast_scan(self):
242-
"""This command sends a series of fastscan message
245+
"""This command sends a series of fastscan message
243246
to find unconfigured slave with lowest number of LSS idenities
244247
245248
:return:
246249
True if a slave is found.
247-
False if there is no candidate.
250+
False if there is no candidate.
248251
list is the LSS identities [vendor_id, product_code, revision_number, serial_number]
249252
:rtype: bool, list
250253
"""
@@ -353,7 +356,7 @@ def __send_configure(self, req_cs, value1=0, value2=0):
353356
raise LssError("Response message is not for the request")
354357

355358
if error_code != ERROR_NONE:
356-
error_msg = "LSS Error: %d" % error_code
359+
error_msg = f"LSS Error: {error_code}"
357360
raise LssError(error_msg)
358361

359362
def __send_command(self, message):
@@ -368,9 +371,7 @@ def __send_command(self, message):
368371
:rtype: bytes
369372
"""
370373

371-
message_str = " ".join(["{:02x}".format(x) for x in message])
372-
logger.info(
373-
"Sending LSS message {}".format(message_str))
374+
logger.info("Sending LSS message %s", message.hex(" ").upper())
374375

375376
response = None
376377
if not self.responses.empty():

0 commit comments

Comments
 (0)