Browse Source

added command `cc1101-transmit` to transmit via command-line interface

Fabian Peter Hammerle 3 months ago
parent
commit
9d34140b5b
5 changed files with 260 additions and 3 deletions
  1. 2 0
      CHANGELOG.md
  2. 15 2
      README.md
  3. 80 0
      cc1101/_cli.py
  4. 2 1
      setup.py
  5. 161 0
      tests/test_cli_transmit.py

+ 2 - 0
CHANGELOG.md

@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## [Unreleased]
+### Added
+- added command `cc1101-transmit` to transmit via command-line interface
 
 ## [2.4.0] - 2020-12-13
 ### Added

+ 15 - 2
README.md

@@ -6,7 +6,7 @@
 [![Compatible Python Versions](https://img.shields.io/pypi/pyversions/cc1101.svg)](https://pypi.org/project/cc1101/)
 [![DOI](https://zenodo.org/badge/292333844.svg)](https://zenodo.org/badge/latestdoi/292333844)
 
-Python Library to Transmit RF Signals via [CC1101 Transceivers](https://www.ti.com/product/CC1101)
+Python Library & Command Line Tool to Transmit RF Signals via [CC1101 Transceivers](https://www.ti.com/product/CC1101)
 
 ## Setup
 
@@ -43,6 +43,8 @@ Raspberry Pi GPIO docs: https://www.raspberrypi.org/documentation/usage/gpio/
 
 ## Usage
 
+### Library
+
 See [examples](https://github.com/fphammerle/python-cc1101/blob/master/examples/).
 
 ```python
@@ -58,9 +60,20 @@ In case CC1101 is connected to a different SPI bus or chip select line
 than `/dev/spidev0.0`,
 use `CC1101(spi_bus=?, spi_chip_select=?)`.
 
+### Command Line
+
+```sh
+$ printf '\x01\x02\x03' | cc1101-transmit -f 433920000 -r 1000
+```
+
+See `cc1101-transmit --help`.
+
+### Troubleshooting
+
 In case a `PermissionError` gets raised,
 check the permissions of `/dev/spidev*`.
 You'll probably need `sudo usermod -a -G spi $USER`,
 followed by a re-login.
 
-CC1101's docs: https://www.ti.com/lit/ds/symlink/cc1101.pdf
+Consult CC1101's offical docs for an in-depth explanation of all options:
+https://www.ti.com/lit/ds/symlink/cc1101.pdf

+ 80 - 0
cc1101/_cli.py

@@ -0,0 +1,80 @@
+# python-cc1101 - Python Library to Transmit RF Signals via C1101 Transceivers
+#
+# Copyright (C) 2020 Fabian Peter Hammerle <fabian@hammerle.me>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+import argparse
+import logging
+import sys
+
+import cc1101
+import cc1101.options
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def _transmit():
+    argparser = argparse.ArgumentParser(
+        description="Transmits the payload provided via standard input (stdin)"
+        " OOK-modulated in big-endian bit order.",
+        allow_abbrev=False,
+    )
+    argparser.add_argument("-f", "--base-frequency-hertz", type=int)
+    argparser.add_argument("-r", "--symbol-rate-baud", type=int)
+    argparser.add_argument(
+        "-s",
+        "--sync-mode",
+        type=str,
+        choices=[m.name.lower().replace("_", "-") for m in cc1101.options.SyncMode],
+    )
+    argparser.add_argument(
+        "-l",
+        "--packet-length-mode",
+        type=str,
+        choices=[m.name.lower() for m in cc1101.options.PacketLengthMode],
+    )
+    argparser.add_argument("--disable-checksum", action="store_true")
+    argparser.add_argument("-d", "--debug", action="store_true")
+    args = argparser.parse_args()
+    logging.basicConfig(
+        level=logging.DEBUG if args.debug else logging.INFO,
+        # format="%(asctime)s:%(levelname)s:%(name)s:%(funcName)s:%(message)s",
+        format="%(name)s:%(funcName)s:%(message)s",
+        datefmt="%Y-%m-%dT%H:%M:%S%z",
+    )
+    _LOGGER.debug("args=%r", args)
+    with cc1101.CC1101() as transceiver:
+        if args.base_frequency_hertz:
+            transceiver.set_base_frequency_hertz(args.base_frequency_hertz)
+        if args.symbol_rate_baud:
+            transceiver.set_symbol_rate_baud(args.symbol_rate_baud)
+        if args.sync_mode:
+            transceiver.set_sync_mode(
+                cc1101.options.SyncMode[args.sync_mode.upper().replace("-", "_")]
+            )
+        payload = sys.stdin.buffer.read()
+        if args.packet_length_mode:
+            packet_length_mode = cc1101.options.PacketLengthMode[
+                args.packet_length_mode.upper()
+            ]
+            # default: variable length
+            transceiver.set_packet_length_mode(packet_length_mode)
+            # default: 255 (maximum)
+            if packet_length_mode == cc1101.options.PacketLengthMode.FIXED:
+                transceiver.set_packet_length_bytes(len(payload))
+        if args.disable_checksum:
+            transceiver.disable_checksum()
+        _LOGGER.debug("transceiver=%r", transceiver)
+        transceiver.transmit(payload)

+ 2 - 1
setup.py

@@ -25,7 +25,7 @@ setuptools.setup(
     name="cc1101",
     use_scm_version=True,
     packages=setuptools.find_packages(),
-    description="Python Library to Transmit RF Signals via C1101 Transceivers",
+    description="Python Library & Command Line Tool to Transmit RF Signals via C1101 Transceivers",
     long_description=pathlib.Path(__file__).parent.joinpath("README.md").read_text(),
     long_description_content_type="text/markdown",
     author="Fabian Peter Hammerle",
@@ -57,6 +57,7 @@ setuptools.setup(
         "Topic :: Home Automation",
         "Topic :: Communications",
     ],
+    entry_points={"console_scripts": ["cc1101-transmit = cc1101._cli:_transmit"]},
     # apt install python3-spidev
     # https://github.com/doceme/py-spidev
     install_requires=["spidev"],

+ 161 - 0
tests/test_cli_transmit.py

@@ -0,0 +1,161 @@
+# python-cc1101 - Python Library to Transmit RF Signals via C1101 Transceivers
+#
+# Copyright (C) 2020 Fabian Peter Hammerle <fabian@hammerle.me>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+import io
+import logging
+import unittest.mock
+
+import pytest
+
+import cc1101._cli
+from cc1101.options import PacketLengthMode, SyncMode
+
+# pylint: disable=protected-access
+
+
+@pytest.mark.parametrize(
+    (
+        "args",
+        "base_frequency_hertz",
+        "symbol_rate_baud",
+        "sync_mode",
+        "packet_length_mode",
+        "checksum_disabled",
+    ),
+    (
+        ([""], None, None, None, None, False),
+        (["", "-f", "433920000"], 433920000, None, None, None, False),
+        (
+            ["", "--base-frequency-hertz", "868000000"],
+            868000000,
+            None,
+            None,
+            None,
+            False,
+        ),
+        (["", "-r", "10000"], None, 10000, None, None, False),
+        (["", "--symbol-rate-baud", "12345"], None, 12345, None, None, False),
+        (
+            ["", "-s", "no-preamble-and-sync-word"],
+            None,
+            None,
+            SyncMode.NO_PREAMBLE_AND_SYNC_WORD,
+            None,
+            False,
+        ),
+        (
+            ["", "--sync-mode", "no-preamble-and-sync-word"],
+            None,
+            None,
+            SyncMode.NO_PREAMBLE_AND_SYNC_WORD,
+            None,
+            False,
+        ),
+        (
+            ["", "--sync-mode", "transmit-16-match-15-bits"],
+            None,
+            None,
+            SyncMode.TRANSMIT_16_MATCH_15_BITS,
+            None,
+            False,
+        ),
+        (["", "-l", "fixed"], None, None, None, PacketLengthMode.FIXED, False),
+        (
+            ["", "--packet-length-mode", "fixed"],
+            None,
+            None,
+            None,
+            PacketLengthMode.FIXED,
+            False,
+        ),
+        (
+            ["", "--packet-length-mode", "variable"],
+            None,
+            None,
+            None,
+            PacketLengthMode.VARIABLE,
+            False,
+        ),
+        (["", "--disable-checksum"], None, None, None, None, True),
+    ),
+)
+@pytest.mark.parametrize("payload", (b"", b"message"))
+def test_configure_device(
+    args,
+    base_frequency_hertz,
+    symbol_rate_baud,
+    sync_mode,
+    packet_length_mode,
+    checksum_disabled,
+    payload,
+):
+    # pylint: disable=too-many-arguments
+    with unittest.mock.patch("cc1101.CC1101") as transceiver_mock:
+        stdin_mock = unittest.mock.MagicMock()
+        stdin_mock.buffer = io.BytesIO(payload)
+        with unittest.mock.patch("sys.stdin", stdin_mock):
+            with unittest.mock.patch("sys.argv", args):
+                cc1101._cli._transmit()
+    transceiver_mock.assert_called_once_with()
+    if base_frequency_hertz is None:
+        transceiver_mock().__enter__().set_base_frequency_hertz.assert_not_called()
+    else:
+        transceiver_mock().__enter__().set_base_frequency_hertz.assert_called_once_with(
+            base_frequency_hertz
+        )
+    if symbol_rate_baud is None:
+        transceiver_mock().__enter__().set_symbol_rate_baud.assert_not_called()
+    else:
+        transceiver_mock().__enter__().set_symbol_rate_baud.assert_called_once_with(
+            symbol_rate_baud
+        )
+    if sync_mode is None:
+        transceiver_mock().__enter__().set_sync_mode.assert_not_called()
+    else:
+        transceiver_mock().__enter__().set_sync_mode.assert_called_once_with(sync_mode)
+    if packet_length_mode is None:
+        transceiver_mock().__enter__().set_packet_length_mode.assert_not_called()
+    else:
+        transceiver_mock().__enter__().set_packet_length_mode.assert_called_once_with(
+            packet_length_mode
+        )
+    if packet_length_mode == PacketLengthMode.FIXED:
+        transceiver_mock().__enter__().set_packet_length_bytes.assert_called_with(
+            len(payload)
+        )
+    else:
+        transceiver_mock().__enter__().set_packet_length_bytes.assert_not_called()
+    if checksum_disabled:
+        transceiver_mock().__enter__().disable_checksum.assert_called_once_with()
+    else:
+        transceiver_mock().__enter__().disable_checksum.assert_not_called()
+
+
+@pytest.mark.parametrize(
+    ("args", "root_log_level"), (([""], logging.INFO), (["", "--debug"], logging.DEBUG))
+)
+def test_root_log_level(args, root_log_level):
+    stdin_mock = unittest.mock.MagicMock()
+    stdin_mock.buffer = io.BytesIO(b"")
+    with unittest.mock.patch("cc1101.CC1101"), unittest.mock.patch(
+        "sys.stdin", stdin_mock
+    ), unittest.mock.patch("sys.argv", args), unittest.mock.patch(
+        "logging.basicConfig"
+    ) as logging_basic_config_mock:
+        cc1101._cli._transmit()
+    assert logging_basic_config_mock.call_count == 1
+    assert logging_basic_config_mock.call_args[1]["level"] == root_log_level