Skip to content

Commit 7c3a004

Browse files
authored
Merge pull request #7 from int-brain-lab/develop
Update documentation.yaml
2 parents 63f0023 + dd11997 commit 7c3a004

File tree

6 files changed

+185
-82
lines changed

6 files changed

+185
-82
lines changed

.github/workflows/documentation.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ on:
55
branches:
66
- main
77
paths:
8-
- docs/**
9-
- tycmd.py
8+
- 'docs/**'
9+
- 'tycmd.py'
1010

1111
permissions:
1212
contents: write

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.2.1] - 2024-08-06
9+
10+
### Changed
11+
12+
- changed default log-level for reset() and upload() to INFO
13+
- added a few examples to README.md
14+
815
## [0.2.0] - 2024-08-06
916

1017
### Added
@@ -35,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3542
_First release._
3643

3744

45+
[0.2.1]: https://github.com/int-brain-lab/tycmd-wrapper/releases/tag/v0.2.1
3846
[0.2.0]: https://github.com/int-brain-lab/tycmd-wrapper/releases/tag/v0.2.0
3947
[0.1.2]: https://github.com/int-brain-lab/tycmd-wrapper/releases/tag/v0.1.2
4048
[0.1.1]: https://github.com/int-brain-lab/tycmd-wrapper/releases/tag/v0.1.1

README.md

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,89 @@
1-
# tycmd-wrapper
1+
tycmd-wrapper
2+
=============
23

3-
A Python wrapper for [tycmd](https://koromix.dev/tytools) by [Niels
4-
Martignène](https://github.com/Koromix/).
4+
A Python wrapper for [tycmd](https://koromix.dev/tytools) by
5+
[Niels Martignène](https://github.com/Koromix/) - a tool for managing
6+
[Teensy USB Development Boards](https://www.pjrc.com/teensy/) by PJRC.
57

6-
Documentation: https://int-brain-lab.github.io/tycmd-wrapper
8+
9+
Examples
10+
--------
11+
12+
### Identifying a firmware file
13+
14+
To identify which models are compatible with a specific firmware file, use the `identify()` method.
15+
16+
```python
17+
import tycmd
18+
compatible_models = tycmd.identify('blink.hex')
19+
```
20+
21+
Models compatible with the firmware file will be returned as a list of strings:
22+
```python
23+
['Teensy 4.0', 'Teensy 4.0 (beta 1)']
24+
```
25+
26+
### List Available Boards
27+
To list all available boards, use the `list_boards()` method.
28+
29+
```python
30+
import tycmd
31+
boards = list_boards()
32+
```
33+
34+
Details for the available boards will be returned as a list of python dictionaries.
35+
```python
36+
[
37+
{
38+
'action': 'add',
39+
'tag': '3576040-Teensy',
40+
'serial': '3576040',
41+
'description': 'USB Serial',
42+
'model': 'Teensy 3.1',
43+
'location': 'usb-1-4',
44+
'capabilities': ['unique', 'run', 'reboot', 'serial'],
45+
'interfaces': [['Serial', '/dev/ttyACM1']],
46+
},
47+
{
48+
'action': 'add',
49+
'tag': '14014980-Teensy',
50+
'serial': '14014980',
51+
'description': 'USB Serial',
52+
'model': 'Teensy 4.0',
53+
'location': 'usb-1-3',
54+
'capabilities': ['unique', 'run', 'rtc', 'reboot', 'serial'],
55+
'interfaces': [['Serial', '/dev/ttyACM0']],
56+
},
57+
]
58+
```
59+
60+
### Uploading a firmware file
61+
62+
To upload a firmware file to a board, use the `upload()` method.
63+
You can specify a board by its port or by its serial number.
64+
65+
```python
66+
import tycmd
67+
import logging
68+
69+
logging.basicConfig(level=logging.INFO)
70+
tycmd.upload('blink.hex', port='/dev/ttyACM0')
71+
```
72+
73+
The upload progress will be logged:
74+
```
75+
INFO:tycmd:Uploading to board '14014980-Teensy' (Teensy 4.0)
76+
INFO:tycmd:Triggering board reboot
77+
INFO:tycmd:Firmware: blink40.hex
78+
INFO:tycmd:Flash usage: 19 kiB (1.0%)
79+
INFO:tycmd:Uploading...
80+
INFO:tycmd:Sending reset command (with RTC)
81+
```
82+
83+
Full Documentation
84+
------------------
85+
86+
The full API documentation is available [here](https://int-brain-lab.github.io/tycmd-wrapper).
787

888
![License](https://img.shields.io/github/license/int-brain-lab/tycmd-wrapper)
989
[![Coverage](https://img.shields.io/coverallsCoverage/github/int-brain-lab/tycmd-wrapper)](https://coveralls.io/github/int-brain-lab/tycmd-wrapper)

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ doc = [
5555
[tool.ruff]
5656
include = ["pyproject.toml", "tycmd.py", "tests/*.py"]
5757

58+
[tool.ruff.format]
59+
quote-style = "single"
60+
5861
[tool.ruff.lint]
5962
extend-select = ["D"]
6063

tests/test_tycmd.py

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,24 @@
77

88
import tycmd
99

10-
BLINK40_HEX = Path(__file__).parent.joinpath("blink40.hex").resolve()
11-
BLINK41_HEX = Path(__file__).parent.joinpath("blink41.hex").resolve()
10+
BLINK40_HEX = Path(__file__).parent.joinpath('blink40.hex').resolve()
11+
BLINK41_HEX = Path(__file__).parent.joinpath('blink41.hex').resolve()
1212

1313

1414
@pytest.fixture
1515
def mock_Popen():
16-
with patch("tycmd.Popen", autospec=True) as mock_Popen:
16+
with patch('tycmd.Popen', autospec=True) as mock_Popen:
1717
context = mock_Popen.return_value.__enter__.return_value
1818

19-
def set_pipes(stdout: list[str], stderr: list[str]):
19+
def set_pipes(stdout: list[str] = [], stderr: list[str] = []):
2020
context.stdout = stdout
2121
context.stderr = stderr
2222
context.communicate.return_value = (
23-
"\n".join(context.stdout),
24-
"\n".join(context.stderr),
23+
'\n'.join(context.stdout),
24+
'\n'.join(context.stderr),
2525
)
2626

27-
def set_returncode(returncode: int):
27+
def set_returncode(returncode: int = 0):
2828
context.returncode = returncode
2929

3030
mock_Popen.set_pipes = set_pipes
@@ -35,30 +35,38 @@ def set_returncode(returncode: int):
3535
yield mock_Popen
3636

3737

38-
def test_upload(mock_Popen):
38+
def test_upload(mock_Popen, caplog):
39+
mock_Popen.set_pipes(stdout=['output'])
40+
caplog.set_level(logging.INFO)
3941
tycmd.upload(BLINK40_HEX, check=True, reset_board=True)
40-
assert "--nocheck" not in mock_Popen.call_args[0][0]
41-
assert "--noreset" not in mock_Popen.call_args[0][0]
42-
assert "--rtc" in mock_Popen.call_args[0][0]
43-
assert "--quiet" not in mock_Popen.call_args[0][0]
42+
assert '--nocheck' not in mock_Popen.call_args[0][0]
43+
assert '--noreset' not in mock_Popen.call_args[0][0]
44+
assert '--rtc' in mock_Popen.call_args[0][0]
45+
assert '--quiet' not in mock_Popen.call_args[0][0]
46+
assert len(caplog.records) > 0
47+
assert all([x.levelname == 'INFO' for x in caplog.records])
48+
49+
caplog.clear()
4450
tycmd.upload(BLINK40_HEX, check=False, reset_board=False, log_level=logging.NOTSET)
45-
assert "--nocheck" in mock_Popen.call_args[0][0]
46-
assert "--noreset" in mock_Popen.call_args[0][0]
47-
assert "--rtc" in mock_Popen.call_args[0][0]
48-
assert "--quiet" in mock_Popen.call_args[0][0]
51+
assert '--nocheck' in mock_Popen.call_args[0][0]
52+
assert '--noreset' in mock_Popen.call_args[0][0]
53+
assert '--rtc' in mock_Popen.call_args[0][0]
54+
assert '--quiet' in mock_Popen.call_args[0][0]
55+
assert len(caplog.records) == 0
4956

5057

5158
def test_reset(mock_Popen, caplog):
52-
output = tycmd.reset(bootloader=True, log_level=0)
59+
mock_Popen.set_pipes(['status'], [])
60+
caplog.set_level(logging.INFO)
61+
tycmd.reset(bootloader=True, log_level=logging.NOTSET)
5362
mock_Popen.assert_called_once()
54-
assert "--bootloader" in mock_Popen.call_args[0][0]
63+
assert '--bootloader' in mock_Popen.call_args[0][0]
5564
assert len(caplog.records) == 0
56-
assert output is None
5765

58-
mock_Popen.set_pipes(["status"], [])
59-
tycmd.reset(log_level=30)
60-
assert "--bootloader" not in mock_Popen.call_args[0][0]
61-
assert "status" in caplog.text
66+
tycmd.reset()
67+
assert '--bootloader' not in mock_Popen.call_args[0][0]
68+
assert len(caplog.records) > 0
69+
assert all([x.levelname == 'INFO' for x in caplog.records])
6270

6371
mock_Popen.set_returncode(1)
6472
with pytest.raises(ChildProcessError):
@@ -67,12 +75,12 @@ def test_reset(mock_Popen, caplog):
6775

6876
def test_identify():
6977
with TemporaryDirectory() as temp_directory:
70-
firmware_file = Path(temp_directory).joinpath("firmware.hex")
78+
firmware_file = Path(temp_directory).joinpath('firmware.hex')
7179
firmware_file.touch()
7280
with pytest.raises(ChildProcessError):
7381
tycmd.identify(firmware_file)
74-
assert "Teensy 4.0" in tycmd.identify(BLINK40_HEX)
75-
assert "Teensy 4.1" in tycmd.identify(BLINK41_HEX)
82+
assert 'Teensy 4.0' in tycmd.identify(BLINK40_HEX)
83+
assert 'Teensy 4.1' in tycmd.identify(BLINK41_HEX)
7684

7785

7886
def test_list_boards(mock_Popen):
@@ -86,9 +94,9 @@ def test_list_boards(mock_Popen):
8694
output = tycmd.list_boards()
8795
assert isinstance(output, list)
8896
assert isinstance(output[0], dict)
89-
assert output[0]["serial"] == "12345678"
97+
assert output[0]['serial'] == '12345678'
9098

91-
mock_Popen.set_pipes(["[\n]\n"], [])
99+
mock_Popen.set_pipes(['[\n]\n'], [])
92100
output = tycmd.list_boards()
93101
assert isinstance(output, list)
94102
assert len(output) == 0
@@ -97,7 +105,7 @@ def test_list_boards(mock_Popen):
97105
def test_version():
98106
assert tycmd.version() == tycmd._TYCMD_VERSION
99107
with (
100-
patch("tycmd._call_tycmd", return_value="invalid") as _,
108+
patch('tycmd._call_tycmd', return_value='invalid') as _,
101109
pytest.raises(ChildProcessError),
102110
):
103111
tycmd.version()
@@ -107,40 +115,40 @@ def test__parse_firmware_file():
107115
with TemporaryDirectory() as temp_directory:
108116
with pytest.raises(IsADirectoryError):
109117
tycmd._parse_firmware_file(temp_directory)
110-
firmware_file = Path(temp_directory).joinpath("firmware")
118+
firmware_file = Path(temp_directory).joinpath('firmware')
111119
with pytest.raises(FileNotFoundError):
112120
tycmd._parse_firmware_file(firmware_file)
113121
firmware_file.touch()
114122
with pytest.raises(ValueError):
115123
tycmd._parse_firmware_file(firmware_file)
116-
firmware_file = firmware_file.with_suffix(".hex")
124+
firmware_file = firmware_file.with_suffix('.HEX')
117125
firmware_file.touch()
118126
assert tycmd._parse_firmware_file(firmware_file).samefile(firmware_file)
119127
assert tycmd._parse_firmware_file(str(firmware_file)).samefile(firmware_file)
120128

121129

122130
def test__call_tycmd(mock_Popen):
123-
mock_Popen.set_pipes(["status"], ["error!"])
131+
mock_Popen.set_pipes(['status'], ['error!'])
124132
tycmd._call_tycmd([], raise_on_stderr=False)
125133
with pytest.raises(ChildProcessError):
126134
tycmd._call_tycmd([], raise_on_stderr=True)
127135

128-
mock_Popen.set_pipes(["status"], [])
136+
mock_Popen.set_pipes(['status'], [])
129137
mock_Popen.set_returncode(-1)
130138
with pytest.raises(ChildProcessError):
131139
tycmd._call_tycmd([])
132140

133141

134142
def test__assemble_args():
135-
output = tycmd._assemble_args(args=[], serial="serial")
136-
assert "-B serial" in " ".join(output)
137-
output = tycmd._assemble_args(args=[], family="family")
138-
assert "-B -family" in " ".join(output)
139-
output = tycmd._assemble_args(args=[], port="port")
140-
assert "-B @port" in " ".join(output)
143+
output = tycmd._assemble_args(args=[], serial='serial')
144+
assert '-B serial' in ' '.join(output)
145+
output = tycmd._assemble_args(args=[], family='family')
146+
assert '-B -family' in ' '.join(output)
147+
output = tycmd._assemble_args(args=[], port='port')
148+
assert '-B @port' in ' '.join(output)
141149
output = tycmd._assemble_args(
142-
args=["some_argument"], serial="serial", family="family", port="port"
150+
args=['some_argument'], serial='serial', family='family', port='port'
143151
)
144-
assert "-B serial-family@port" in " ".join(output)
145-
assert "tycmd" in output
146-
assert "some_argument" in output
152+
assert '-B serial-family@port' in ' '.join(output)
153+
assert 'tycmd' in output
154+
assert 'some_argument' in output

0 commit comments

Comments
 (0)