Skip to content

Commit

Permalink
Merge pull request #7 from int-brain-lab/develop
Browse files Browse the repository at this point in the history
Update documentation.yaml
  • Loading branch information
bimac authored Aug 6, 2024
2 parents 63f0023 + dd11997 commit 7c3a004
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 82 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/documentation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ on:
branches:
- main
paths:
- docs/**
- tycmd.py
- 'docs/**'
- 'tycmd.py'

permissions:
contents: write
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.1] - 2024-08-06

### Changed

- changed default log-level for reset() and upload() to INFO
- added a few examples to README.md

## [0.2.0] - 2024-08-06

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


[0.2.1]: https://github.com/int-brain-lab/tycmd-wrapper/releases/tag/v0.2.1
[0.2.0]: https://github.com/int-brain-lab/tycmd-wrapper/releases/tag/v0.2.0
[0.1.2]: https://github.com/int-brain-lab/tycmd-wrapper/releases/tag/v0.1.2
[0.1.1]: https://github.com/int-brain-lab/tycmd-wrapper/releases/tag/v0.1.1
Expand Down
88 changes: 84 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,89 @@
# tycmd-wrapper
tycmd-wrapper
=============

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

Documentation: https://int-brain-lab.github.io/tycmd-wrapper

Examples
--------

### Identifying a firmware file

To identify which models are compatible with a specific firmware file, use the `identify()` method.

```python
import tycmd
compatible_models = tycmd.identify('blink.hex')
```

Models compatible with the firmware file will be returned as a list of strings:
```python
['Teensy 4.0', 'Teensy 4.0 (beta 1)']
```

### List Available Boards
To list all available boards, use the `list_boards()` method.

```python
import tycmd
boards = list_boards()
```

Details for the available boards will be returned as a list of python dictionaries.
```python
[
{
'action': 'add',
'tag': '3576040-Teensy',
'serial': '3576040',
'description': 'USB Serial',
'model': 'Teensy 3.1',
'location': 'usb-1-4',
'capabilities': ['unique', 'run', 'reboot', 'serial'],
'interfaces': [['Serial', '/dev/ttyACM1']],
},
{
'action': 'add',
'tag': '14014980-Teensy',
'serial': '14014980',
'description': 'USB Serial',
'model': 'Teensy 4.0',
'location': 'usb-1-3',
'capabilities': ['unique', 'run', 'rtc', 'reboot', 'serial'],
'interfaces': [['Serial', '/dev/ttyACM0']],
},
]
```

### Uploading a firmware file

To upload a firmware file to a board, use the `upload()` method.
You can specify a board by its port or by its serial number.

```python
import tycmd
import logging

logging.basicConfig(level=logging.INFO)
tycmd.upload('blink.hex', port='/dev/ttyACM0')
```

The upload progress will be logged:
```
INFO:tycmd:Uploading to board '14014980-Teensy' (Teensy 4.0)
INFO:tycmd:Triggering board reboot
INFO:tycmd:Firmware: blink40.hex
INFO:tycmd:Flash usage: 19 kiB (1.0%)
INFO:tycmd:Uploading...
INFO:tycmd:Sending reset command (with RTC)
```

Full Documentation
------------------

The full API documentation is available [here](https://int-brain-lab.github.io/tycmd-wrapper).

![License](https://img.shields.io/github/license/int-brain-lab/tycmd-wrapper)
[![Coverage](https://img.shields.io/coverallsCoverage/github/int-brain-lab/tycmd-wrapper)](https://coveralls.io/github/int-brain-lab/tycmd-wrapper)
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ doc = [
[tool.ruff]
include = ["pyproject.toml", "tycmd.py", "tests/*.py"]

[tool.ruff.format]
quote-style = "single"

[tool.ruff.lint]
extend-select = ["D"]

Expand Down
94 changes: 51 additions & 43 deletions tests/test_tycmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@

import tycmd

BLINK40_HEX = Path(__file__).parent.joinpath("blink40.hex").resolve()
BLINK41_HEX = Path(__file__).parent.joinpath("blink41.hex").resolve()
BLINK40_HEX = Path(__file__).parent.joinpath('blink40.hex').resolve()
BLINK41_HEX = Path(__file__).parent.joinpath('blink41.hex').resolve()


@pytest.fixture
def mock_Popen():
with patch("tycmd.Popen", autospec=True) as mock_Popen:
with patch('tycmd.Popen', autospec=True) as mock_Popen:
context = mock_Popen.return_value.__enter__.return_value

def set_pipes(stdout: list[str], stderr: list[str]):
def set_pipes(stdout: list[str] = [], stderr: list[str] = []):
context.stdout = stdout
context.stderr = stderr
context.communicate.return_value = (
"\n".join(context.stdout),
"\n".join(context.stderr),
'\n'.join(context.stdout),
'\n'.join(context.stderr),
)

def set_returncode(returncode: int):
def set_returncode(returncode: int = 0):
context.returncode = returncode

mock_Popen.set_pipes = set_pipes
Expand All @@ -35,30 +35,38 @@ def set_returncode(returncode: int):
yield mock_Popen


def test_upload(mock_Popen):
def test_upload(mock_Popen, caplog):
mock_Popen.set_pipes(stdout=['output'])
caplog.set_level(logging.INFO)
tycmd.upload(BLINK40_HEX, check=True, reset_board=True)
assert "--nocheck" not in mock_Popen.call_args[0][0]
assert "--noreset" not in mock_Popen.call_args[0][0]
assert "--rtc" in mock_Popen.call_args[0][0]
assert "--quiet" not in mock_Popen.call_args[0][0]
assert '--nocheck' not in mock_Popen.call_args[0][0]
assert '--noreset' not in mock_Popen.call_args[0][0]
assert '--rtc' in mock_Popen.call_args[0][0]
assert '--quiet' not in mock_Popen.call_args[0][0]
assert len(caplog.records) > 0
assert all([x.levelname == 'INFO' for x in caplog.records])

caplog.clear()
tycmd.upload(BLINK40_HEX, check=False, reset_board=False, log_level=logging.NOTSET)
assert "--nocheck" in mock_Popen.call_args[0][0]
assert "--noreset" in mock_Popen.call_args[0][0]
assert "--rtc" in mock_Popen.call_args[0][0]
assert "--quiet" in mock_Popen.call_args[0][0]
assert '--nocheck' in mock_Popen.call_args[0][0]
assert '--noreset' in mock_Popen.call_args[0][0]
assert '--rtc' in mock_Popen.call_args[0][0]
assert '--quiet' in mock_Popen.call_args[0][0]
assert len(caplog.records) == 0


def test_reset(mock_Popen, caplog):
output = tycmd.reset(bootloader=True, log_level=0)
mock_Popen.set_pipes(['status'], [])
caplog.set_level(logging.INFO)
tycmd.reset(bootloader=True, log_level=logging.NOTSET)
mock_Popen.assert_called_once()
assert "--bootloader" in mock_Popen.call_args[0][0]
assert '--bootloader' in mock_Popen.call_args[0][0]
assert len(caplog.records) == 0
assert output is None

mock_Popen.set_pipes(["status"], [])
tycmd.reset(log_level=30)
assert "--bootloader" not in mock_Popen.call_args[0][0]
assert "status" in caplog.text
tycmd.reset()
assert '--bootloader' not in mock_Popen.call_args[0][0]
assert len(caplog.records) > 0
assert all([x.levelname == 'INFO' for x in caplog.records])

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

def test_identify():
with TemporaryDirectory() as temp_directory:
firmware_file = Path(temp_directory).joinpath("firmware.hex")
firmware_file = Path(temp_directory).joinpath('firmware.hex')
firmware_file.touch()
with pytest.raises(ChildProcessError):
tycmd.identify(firmware_file)
assert "Teensy 4.0" in tycmd.identify(BLINK40_HEX)
assert "Teensy 4.1" in tycmd.identify(BLINK41_HEX)
assert 'Teensy 4.0' in tycmd.identify(BLINK40_HEX)
assert 'Teensy 4.1' in tycmd.identify(BLINK41_HEX)


def test_list_boards(mock_Popen):
Expand All @@ -86,9 +94,9 @@ def test_list_boards(mock_Popen):
output = tycmd.list_boards()
assert isinstance(output, list)
assert isinstance(output[0], dict)
assert output[0]["serial"] == "12345678"
assert output[0]['serial'] == '12345678'

mock_Popen.set_pipes(["[\n]\n"], [])
mock_Popen.set_pipes(['[\n]\n'], [])
output = tycmd.list_boards()
assert isinstance(output, list)
assert len(output) == 0
Expand All @@ -97,7 +105,7 @@ def test_list_boards(mock_Popen):
def test_version():
assert tycmd.version() == tycmd._TYCMD_VERSION
with (
patch("tycmd._call_tycmd", return_value="invalid") as _,
patch('tycmd._call_tycmd', return_value='invalid') as _,
pytest.raises(ChildProcessError),
):
tycmd.version()
Expand All @@ -107,40 +115,40 @@ def test__parse_firmware_file():
with TemporaryDirectory() as temp_directory:
with pytest.raises(IsADirectoryError):
tycmd._parse_firmware_file(temp_directory)
firmware_file = Path(temp_directory).joinpath("firmware")
firmware_file = Path(temp_directory).joinpath('firmware')
with pytest.raises(FileNotFoundError):
tycmd._parse_firmware_file(firmware_file)
firmware_file.touch()
with pytest.raises(ValueError):
tycmd._parse_firmware_file(firmware_file)
firmware_file = firmware_file.with_suffix(".hex")
firmware_file = firmware_file.with_suffix('.HEX')
firmware_file.touch()
assert tycmd._parse_firmware_file(firmware_file).samefile(firmware_file)
assert tycmd._parse_firmware_file(str(firmware_file)).samefile(firmware_file)


def test__call_tycmd(mock_Popen):
mock_Popen.set_pipes(["status"], ["error!"])
mock_Popen.set_pipes(['status'], ['error!'])
tycmd._call_tycmd([], raise_on_stderr=False)
with pytest.raises(ChildProcessError):
tycmd._call_tycmd([], raise_on_stderr=True)

mock_Popen.set_pipes(["status"], [])
mock_Popen.set_pipes(['status'], [])
mock_Popen.set_returncode(-1)
with pytest.raises(ChildProcessError):
tycmd._call_tycmd([])


def test__assemble_args():
output = tycmd._assemble_args(args=[], serial="serial")
assert "-B serial" in " ".join(output)
output = tycmd._assemble_args(args=[], family="family")
assert "-B -family" in " ".join(output)
output = tycmd._assemble_args(args=[], port="port")
assert "-B @port" in " ".join(output)
output = tycmd._assemble_args(args=[], serial='serial')
assert '-B serial' in ' '.join(output)
output = tycmd._assemble_args(args=[], family='family')
assert '-B -family' in ' '.join(output)
output = tycmd._assemble_args(args=[], port='port')
assert '-B @port' in ' '.join(output)
output = tycmd._assemble_args(
args=["some_argument"], serial="serial", family="family", port="port"
args=['some_argument'], serial='serial', family='family', port='port'
)
assert "-B serial-family@port" in " ".join(output)
assert "tycmd" in output
assert "some_argument" in output
assert '-B serial-family@port' in ' '.join(output)
assert 'tycmd' in output
assert 'some_argument' in output
Loading

0 comments on commit 7c3a004

Please sign in to comment.