diff --git a/.github/workflows/python-publish.yml b/.github/workflows/release.yml similarity index 87% rename from .github/workflows/python-publish.yml rename to .github/workflows/release.yml index feff0ee..3cab53b 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/release.yml @@ -13,24 +13,24 @@ permissions: jobs: deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v3 with: python-version: '3.9' - - name: Install dependencies + - name: Install build dependencies run: | python -m pip install --upgrade pip if [ -f requirements-deploy.txt ]; then pip install -r requirements-deploy.txt; fi - name: Build package run: | - python update_version.py \ + changelog2version \ --changelog_file changelog.md \ --version_file nextion/version.py \ + --version_file_type py \ --debug python setup.py sdist - name: Publish package diff --git a/.github/workflows/test-release.yaml b/.github/workflows/test-release.yaml new file mode 100644 index 0000000..ca88db4 --- /dev/null +++ b/.github/workflows/test-release.yaml @@ -0,0 +1,58 @@ +# this file is *not* meant to cover or endorse the use of GitHub Actions, but rather to +# help make automated releases for this project + +name: Upload Python Package to test.pypi.org + +on: [pull_request] + +permissions: + contents: read + +jobs: + test-deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.9' + - name: Install build dependencies + run: | + if [ -f requirements-deploy.txt ]; then pip install -r requirements-deploy.txt; fi + - name: Build package + run: | + changelog2version \ + --changelog_file changelog.md \ + --version_file nextion/version.py \ + --version_file_type py \ + --additional_version_info="-rc${{ github.run_number }}.dev${{ github.event.number }}" \ + --debug + # micropython-nextion is owned by someone else on test.pypi.org + # rename package only for this case + sed -i \ + "s/name\='micropython-nextion'/name\='be-micropython-nextion'/" \ + setup.py + python setup.py sdist + - name: Test built package + # sdist call creates non twine conform "*.orig" files, remove them + run: | + rm dist/*.orig + twine check dist/*.tar.gz + - name: Archive build package artifact + uses: actions/upload-artifact@v3 + with: + # https://docs.github.com/en/actions/learn-github-actions/contexts#github-context + # ${{ github.repository }} and ${{ github.ref_name }} can't be used for artifact name due to unallowed '/' + name: dist_repo.${{ github.event.repository.name }}_sha.${{ github.sha }}_build.${{ github.run_number }} + path: dist/*.tar.gz + retention-days: 14 + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1.5 + with: + repository_url: https://test.pypi.org/legacy/ + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + skip_existing: true + verbose: true + print_hash: true diff --git a/.github/workflows/python-test.yml b/.github/workflows/test.yml similarity index 71% rename from .github/workflows/python-test.yml rename to .github/workflows/test.yml index d54f23b..81d3806 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/test.yml @@ -15,19 +15,13 @@ permissions: jobs: build: - runs-on: ubuntu-latest - # strategy: - # fail-fast: false - # matrix: - # python-version: ["3.8", "3.9", "3.10"] - steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v3 with: - # python-version: ${{ matrix.python-version }} python-version: '3.9' - name: Install dependencies run: | @@ -63,20 +57,13 @@ jobs: if [ -f requirements-deploy.txt ]; then pip install -r requirements-deploy.txt; fi - name: Build package run: | - python update_version.py \ + changelog2version \ --changelog_file changelog.md \ --version_file nextion/version.py \ + --version_file_type py \ --debug python setup.py sdist + rm dist/*.orig - name: Test build package run: | - twine check dist/*.tar.gz - - name: Archive build package artifact - uses: actions/upload-artifact@v3 - with: - # https://docs.github.com/en/actions/learn-github-actions/contexts#github-context - # ${{ github.repository }} and ${{ github.ref_name }} can't be used for artifact name due to unallowed '/' - name: dist_repo.${{ github.event.repository.name }}_sha.${{ github.sha }}_build.${{ github.run_number }} - # _py.${{ matrix.python-version }} - path: dist/*.tar.gz - retention-days: 14 + twine check dist/* diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..af14205 --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,60 @@ +# MicroPython Nextion library + +[![Downloads](https://pepy.tech/badge/micropython-nextion)](https://pepy.tech/project/micropython-nextion) +![Release](https://img.shields.io/github/v/release/brainelectronics/micropython-nextion?include_prereleases&color=success) +![MicroPython](https://img.shields.io/badge/micropython-Ok-green.svg) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + +MicroPython Nextion library + +--------------- + +## Get started + +This is a quickstart guide to flash the +[MicroPython firmware][ref-upy-firmware-download], connect to a network and +install the MicroPython Nextion library on the board + +### Flash firmware + +```bash +esptool.py --chip esp32 --port /dev/tty.SLAB_USBtoUART erase_flash +esptool.py --chip esp32 --port /dev/tty.SLAB_USBtoUART --baud 921600 write_flash -z 0x1000 esp32spiram-20220117-v1.18.bin +``` + +### Install package on board with pip + +```bash +rshell -p /dev/tty.SLAB_USBtoUART --editor nano +``` + +Inside the rshell + +```bash +cp examples/progressbar/main.py /pyboard +cp examples/boot.py /pyboard +repl +``` + +Inside the REPL + +```python +import machine +import network +import time +import upip + +station = network.WLAN(network.STA_IF) +station.active(True) +station.connect('SSID', 'PASSWORD') +time.sleep(1) +print('Device connected to network: {}'.format(station.isconnected())) + +upip.install('micropython-nextion') + +print('Installation completed') +machine.soft_reset() +``` + + +[ref-upy-firmware-download]: https://micropython.org/download/ diff --git a/README.md b/README.md index 5ef958c..64c693c 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,8 @@ in case no CP210x is used but a CH34x. rshell --port /dev/tty.SLAB_USBtoUART --editor nano ``` -Perform the following command to copy all files and folders to the device +Perform the following command inside the `rshell` to copy all files and +folders to the device ```bash mkdir /pyboard/lib @@ -78,7 +79,7 @@ mkdir /pyboard/lib/nextion cp nextion/* /pyboard/lib/nextion -cp examples/main.py /pyboard +cp examples/basic/main.py /pyboard cp examples/boot.py /pyboard ``` diff --git a/changelog.md b/changelog.md index bd7a973..a0ffc1e 100644 --- a/changelog.md +++ b/changelog.md @@ -17,6 +17,32 @@ r"^\#\# \[\d{1,}[.]\d{1,}[.]\d{1,}\] \- \d{4}\-\d{2}-\d{2}$" --> ## Released +## [0.13.0] - 2022-10-23 +### Added +- `changelog2version` to [requirements file](requirements-deploy.txt) +- [GitHub CI test release workflow](.github/workflows/test-release.yml) to + deploy package as `be-micropython-nextion` to + [Test Python Package Index](https://test.pypi.org/) on every PR +- [Quickstart guide](QUICKSTART.md) + +### Changed +- Use [`changelog2version`][ref-changelog2version-package] to create package + version file, see [#19][ref-issue-19], in: + - [GitHub CI test workflow](.github/workflows/test.yml) + - [GitHub CI release workflow](.github/workflows/release.yml) + +### Removed +- Script `update_version.py` to update version file + +### Fixed +- Flake8 warning in [`nextion_waveform`](nextion/nextion_waveform.py) and + [`nextion_hardware`](nextion/nextion_hardware.py), see [#17][ref-issue-17] +- Use `self._logger.*` instead of `self._nh._logger.*` in: + - [`nextion_rtc`](nextion/nextion_rtc.py) + - [`nextion_upload`](nextion/nextion_upload.py) +- Path of `main.py` to copy manually to the MicroPython board described in + [`README`](README.md) + ## [0.12.0] - 2022-07-30 ### Added - Support `NexRtc` usage with [`nextion_rtc`](nextion/nextion_rtc.py) @@ -164,8 +190,9 @@ r"^\#\# \[\d{1,}[.]\d{1,}[.]\d{1,}\] \- \d{4}\-\d{2}-\d{2}$" - [Example HMI file](examples/everything.HMI) to be used for all examples -[Unreleased]: https://github.com/brainelectronics/micropython-nextion/compare/0.12.0...develop +[Unreleased]: https://github.com/brainelectronics/micropython-nextion/compare/0.13.0...develop +[0.13.0]: https://github.com/brainelectronics/micropython-nextion/tree/0.13.0 [0.12.0]: https://github.com/brainelectronics/micropython-nextion/tree/0.12.0 [0.11.0]: https://github.com/brainelectronics/micropython-nextion/tree/0.11.0 [0.10.0]: https://github.com/brainelectronics/micropython-nextion/tree/0.10.0 @@ -180,6 +207,7 @@ r"^\#\# \[\d{1,}[.]\d{1,}[.]\d{1,}\] \- \d{4}\-\d{2}-\d{2}$" [0.2.0]: https://github.com/brainelectronics/micropython-nextion/tree/0.2.0 [0.1.0]: https://github.com/brainelectronics/micropython-nextion/tree/0.1.0 - +[ref-issue-17]: https://github.com/brainelectronics/micropython-nextion/issues/17 +[ref-issue-19]: https://github.com/brainelectronics/micropython-nextion/issues/19 + +[ref-changelog2version-package]: https://pypi.org/project/changelog2version/ diff --git a/nextion/nextion_hardware.py b/nextion/nextion_hardware.py index 4020474..a2370a9 100644 --- a/nextion/nextion_hardware.py +++ b/nextion/nextion_hardware.py @@ -198,7 +198,7 @@ def recvRetNumber(self, timeout: int = 100) -> int: if ((temp[0] == Const.NEX_RET_NUMBER_HEAD) and (temp[5] == 0xFF) and (temp[6] == 0xFF) and (temp[7] == 0xFF)): - number = (temp[4] << 24) | (temp[3] << 16) | (temp[2] << 8) | (temp[1]) + number = (temp[4] << 24) | (temp[3] << 16) | (temp[2] << 8) | (temp[1]) # noqa # ret = True # if ret: diff --git a/nextion/nextion_rtc.py b/nextion/nextion_rtc.py index 25138be..d8401b2 100644 --- a/nextion/nextion_rtc.py +++ b/nextion/nextion_rtc.py @@ -83,9 +83,8 @@ def write_rtc_time(self, *args, **kwargs) -> bool: format("2016,11,25,12,34,50", [2016, 11, 25, 12, 34, 50])) - self._nh._logger.debug("Timestamp (ISO8601): {}-{}-{}T{}:{}:{}". - format(year, month, day, hour, minute, - second)) + self._logger.debug("Timestamp (ISO8601): {}-{}-{}T{}:{}:{}". + format(year, month, day, hour, minute, second)) cmd = "rtc0={}".format(year) self._nh.sendCommand(cmd) @@ -122,7 +121,7 @@ def write_rtc_time(self, *args, **kwargs) -> bool: else: raise NexRtcError("Either use keyword or positional args") - self._nh._logger.debug("Set '{}' to '{}'".format(time_type, time)) + self._logger.debug("Set '{}' to '{}'".format(time_type, time)) rtc_index = self.time_types.index(time_type.lower()) cmd = "rtc{}={}".format(rtc_index, time) diff --git a/nextion/nextion_upload.py b/nextion/nextion_upload.py index 3eeb4d7..d6506b1 100644 --- a/nextion/nextion_upload.py +++ b/nextion/nextion_upload.py @@ -112,7 +112,7 @@ def upload(self) -> bool: if not self._downloadTftFile(): raise NexUploadError("Download file error") - self._nh._logger.debug("Download ok") + self._logger.debug("Download ok") return True def _checkFile(self) -> bool: @@ -127,13 +127,13 @@ def _checkFile(self) -> bool: # https://docs.python.org/3/library/os.html#os.stat info = stat(self.file_name) self.file_size = info[6] - self._nh._logger.debug("TFT file size is '{}' bytes". - format(self.file_size)) - self._nh._logger.debug("File check ok") + self._logger.debug("TFT file size is '{}' bytes". + format(self.file_size)) + self._logger.debug("File check ok") result = True else: - self._nh._logger.debug("File '{}' does not exist". - format(self.file_name)) + self._logger.debug("File '{}' does not exist". + format(self.file_name)) return result def _getBaudrate(self) -> int: @@ -147,13 +147,13 @@ def _getBaudrate(self) -> int: _baudrate = 0 for baudrate in baudrate_array: - self._nh._logger.debug("Checking connection with '{}' baud". - format(baudrate)) + self._logger.debug("Checking connection with '{}' baud". + format(baudrate)) if self._searchBaudrate(baudrate): _baudrate = baudrate - self._nh._logger.debug("Success, baudrate set to '{}' baud". - format(_baudrate)) + self._logger.debug("Success, baudrate set to '{}' baud". + format(_baudrate)) return _baudrate return _baudrate @@ -174,8 +174,8 @@ def _searchBaudrate(self, baudrate: int) -> bool: self._nh.sendCommand("connect") sleep(0.1) # necessary, data might not be available otherwise response = self._recvRetString() - self._nh._logger.debug("_searchBaudrate response for '{}' baud: {}". - format(baudrate, response)) + self._logger.debug("_searchBaudrate response for '{}' baud: {}". + format(baudrate, response)) if "comok" in response: return True @@ -193,7 +193,7 @@ def _setDownloadBaudrate(self, baudrate: int) -> bool: :rtype: bool """ cmd = "whmi-wri {},{},0".format(self.file_size, baudrate) - self._nh._logger.debug("Set download baudrate cmd: '{}'".format(cmd)) + self._logger.debug("Set download baudrate cmd: '{}'".format(cmd)) self._nh.sendCommand("") self._nh.sendCommand(cmd) @@ -201,8 +201,8 @@ def _setDownloadBaudrate(self, baudrate: int) -> bool: self._nh._baudrate = baudrate self._nh._uart_init() response = self._recvRetString(500) - self._nh._logger.debug("Set download baudrate response: '{}'". - format(response)) + self._logger.debug("Set download baudrate response: '{}'". + format(response)) if (0x05).to_bytes(1, 'little') in response: return True return False @@ -222,13 +222,13 @@ def _downloadTftFile(self) -> bool: data_size = update_file.readinto(file_content) if not data_size: - self._nh._logger.debug("Reached EOF, update finished") + self._logger.debug("Reached EOF, update finished") break self._nh._uart.write(file_content) response = self._recvRetString(500) - # self._nh._logger.debug("File download response: '{}'". + # self._logger.debug("File download response: '{}'". # format(response)) if (0x05).to_bytes(1, 'little') in response: diff --git a/nextion/nextion_waveform.py b/nextion/nextion_waveform.py index 77c9b6f..e90c4f4 100644 --- a/nextion/nextion_waveform.py +++ b/nextion/nextion_waveform.py @@ -67,7 +67,8 @@ def clearChannel(self, ch: int) -> bool: :rtype: bool """ if (ch > 3) and (ch != 255): - self._logger.debug("Only channel (0-3) or all (255) can be cleared") + self._logger.debug("Only the channels (0-3) or all channels at " + "once (255) can be cleared") return False cmd = "cle {},{}".format(self.cid, ch) diff --git a/requirements-deploy.txt b/requirements-deploy.txt index 3140e39..aac060d 100644 --- a/requirements-deploy.txt +++ b/requirements-deploy.txt @@ -2,9 +2,7 @@ # Avoid fixed versions # # to upload package to PyPi or other package hosts twine>=4.0.1,<5 - -# to parse changelog semver -semver>=2.13.0,<3 +changelog2version>=0.5.0,<1 # # to convert markdown to reStructuredText # m2rr>=0.2.3,<1 diff --git a/update_version.py b/update_version.py deleted file mode 100644 index f4916a2..0000000 --- a/update_version.py +++ /dev/null @@ -1,227 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: UTF-8 -*- - -""" -Update version info in python version file with latest changelog version - -The changelog and package version shall always be aligned. This script can be -used to update the version info line "__version_info__ = ..." of a version.py -file with the latest changelog entry. -This changelog entry shall follow the semantic version pattern, see -https://semver.org/ and shall match the following pattern: - -## [x.y.z] - yyyy-mm-dd - -The line shall start with two hashtags followed by a single space. The semver -with x, y and z as non-negative integers, seperated by a dot and surrounded by -square brackets. Followed by a space, a dash, another space and the ISO8601 -formatted date. Additional timestamps after the data, seperated from the date -by a single space, are optional. -""" - -__author__ = "brainelectronics" -__copyright__ = "MIT License, Copyright (c) 2022 brainelectronics" -__credits__ = ["brainelectronics"] -__version__ = "0.1.0" -__maintainer__ = "brainelectronics" -__email__ = "git@brainelectronics.de" -__status__ = "Beta" - -import argparse -import fileinput -import logging -from pathlib import Path -import re -import semver -from sys import stdout - - -def parser_valid_file(parser: argparse.ArgumentParser, arg: str) -> str: - """ - Determine whether file exists. - :param parser: The parser - :type parser: parser object - :param arg: The file to check - :type arg: str - :raise argparse.ArgumentError: Argument is not a file - :returns: Input file path, parser error is thrown otherwise. - :rtype: str - """ - if not Path(arg).is_file(): - parser.error("The file {} does not exist!".format(arg)) - else: - return arg - - -def parse_arguments() -> argparse.Namespace: - """ - Parse CLI arguments. - :raise argparse.ArgumentError Argparse error - :return: argparse object - """ - parser = argparse.ArgumentParser(description=""" - Update version info file based on changelog entry - """, formatter_class=argparse.ArgumentDefaultsHelpFormatter) - - # default arguments - parser.add_argument('-d', '--debug', - action='store_true', - help='Output logger messages to stderr') - - # specific arguments - parser.add_argument('--changelog_file', - dest='changelog_file', - required=True, - type=lambda x: parser_valid_file(parser, x), - help='Path to changelog file') - - parser.add_argument('--version_file', - dest='version_file', - required=True, - type=lambda x: parser_valid_file(parser, x), - help='Path to version file') - - parsed_args = parser.parse_args() - - return parsed_args - - -def parse_changelog(changelog_file: Path, logger: logging.Logger) -> str: - """ - Parse the changelog for the first matching release version line - - :param changelog_file: The path to the changelog file - :type changelog_file: Path - :param logger: Logger object - :type logger: logging.Logger - - :returns: Extracted semantic version string - :rtype: str - """ - release_version_line_regex = r"^\#\# \[\d{1,}[.]\d{1,}[.]\d{1,}\] \- \d{4}\-\d{2}-\d{2}" - # append "$" to match only ISO8601 dates without additional timestamps - release_version_line = "" - - with open(changelog_file, "r") as f: - for line in f: - match = re.search(release_version_line_regex, line) - if match: - release_version_line = match.group() - break - - logger.debug("First matching release version line: '{}'". - format(release_version_line)) - - return parse_semver_line(release_version_line, logger) - - -def parse_semver_line(release_version_line: str, - logger: logging.Logger) -> str: - """ - Parse a release version line for a semantic version - - :param release_version_line: The release version line as described - :type release_version_line: str - :param logger: Logger object - :type logger: logging.Logger - - :returns: Semantic version string - :rtype: str - """ - # release_version_line = "## [0.2.0] - 2022-05-19" - semver_regex = r"\[\d{1,}[.]\d{1,}[.]\d{1,}\]" - semver_string = "0.0.0" - - # extract semver from release version line - match = re.search(semver_regex, release_version_line) - if match: - semver_string = match.group() - # remove '[' and ']' from semver_string - semver_string = re.sub(r"[\[\]]", "", semver_string) - if not semver.VersionInfo.isvalid(semver_string): - logger.error("Parsed SemVer string is invalid, check format") - raise ValueError("Invalid SemVer string") - logger.debug("Extracted SemVer string: '{}'".format(semver_string)) - else: - logger.warning("No SemVer string found in given release version line") - - # semver_string = "0.2.0" - return semver_string - - -def create_version_info_line(semver_string: str, - logger: logging.Logger) -> str: - """ - Create the version info line used in "version.py" - - :param semver_string: The valid semver string - :type semver_string: str - :param logger: Logger object - :type logger: logging.Logger - - :returns: Complete version info line - :rtype: str - """ - # semver_string = "0.2.0" - ver = semver.VersionInfo.parse(semver_string) - - version_info = (str(ver.major), str(ver.minor), str(ver.patch)) - version_info_line = "__version_info__ = {}".format(version_info) - logger.debug("New version info line: '{}'".format(version_info_line)) - - # version_info_line = "__version_info__ = ('0', '2', '0')" - return version_info_line - - -def update_version_file(version_file: Path, - version_info_line: str, - logger: logging.Logger) -> None: - """ - Update the version file with a new version info line - - :param version_file: The path to the version file - :type version_file: Path - :param version_info_line: The version info line - :type version_info_line: str - :param logger: Logger object - :type logger: logging.Logger - """ - # standard output (print) is redirected to the original file - for line in fileinput.input(version_file, inplace=True): - if line.startswith("__version_info__ = "): - print(version_info_line, end="\n") - else: - # keep line ending - print(line, end="") - - logger.debug("Version file '{}' updated with '{}'". - format(version_file, version_info_line)) - - -def main(): - # parse CLI arguments - args = parse_arguments() - - custom_format = '[%(asctime)s] [%(levelname)-8s] [%(filename)-15s @'\ - ' %(funcName)-15s:%(lineno)4s] %(message)s' - logging.basicConfig(level=logging.INFO, - format=custom_format, - stream=stdout) - logger = logging.getLogger(__name__) - if args.debug: - logger.setLevel(logging.DEBUG) - - here = Path(__file__).parent - changelog_file = (here / args.changelog_file).resolve() - version_file = (here / args.version_file).resolve() - - logger.debug("Using changelog file '{}' to update version file '{}'". - format(changelog_file, version_file)) - - semver_string = parse_changelog(changelog_file, logger) - version_info_line = create_version_info_line(semver_string, logger) - update_version_file(version_file, version_info_line, logger) - - -if __name__ == '__main__': - main()