diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..16fbb91 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,28 @@ + + +## Description + + +## Motivation and Context + + + +## How has this been tested? + + + + +## Screenshots (if appropriate): + +## Types of changes + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) + +## Checklist: + + +- [ ] My code follows the code style of this project. +- [ ] My change requires a change to the documentation. +- [ ] I have updated the documentation accordingly. \ No newline at end of file diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..d288b28 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,32 @@ +# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. +# +# You can adjust the behavior by modifying this file. +# For more information, see: +# https://github.com/actions/stale +name: Mark stale issues and pull requests + +on: + workflow_dispatch: + schedule: + - cron: '*/5 * * * *' + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v9 + with: + days-before-issue-stale: 30 + days-before-issue-close: 14 + stale-issue-label: "stale" + stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." + close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." + days-before-pr-stale: 30 + days-before-pr-close: 14 + stale-pr-message: "This PR is stale because it has been open for 30 days with no activity." + close-pr-message: "This PR was closed because it has been inactive for 14 days since being marked as stale." + repo-token: ${{ secrets.GITHUB_TOKEN }} + operations-per-run: 100 \ No newline at end of file diff --git a/README.md b/README.md index 80133a8..ecb0965 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,14 @@ # Broadlink Reader Broadlink Reader is a Python command-line interface (CLI) tool designed to interact with -Broadlink remotes to learn and capturing IR commands. -This tool allows users to connect to a Broadlink device, learn commands, and save -them to a file for future use. +Broadlink remotes to learn and capture IR/RF commands. ## Features - Connect to Broadlink remote over a network. -- Learn and capture IR commands -- Save captured commands to a specified file in JSON format (can be passed to [SmartIR](https://github.com/smartHomeHub/SmartIR) later. +- Learn and capture IR/RF commands +- Save captured commands to a specified file in JSON format +- Generate "commands.json" file that can be integrated to [SmartIR](https://github.com/smartHomeHub/SmartIR). ## Installation @@ -17,15 +16,13 @@ To get started with Broadlink Reader, clone the repository from GitHub: ```sh git clone https://github.com/JexSrs/broadlink-reader.git -cd broadlink-reader ``` and install the project: + ```sh python3 setup.py install -``` -or -```sh +# or pip install -r requirements.txt ``` @@ -38,16 +35,20 @@ interaction with the Broadlink device: python main.py --ip 192.168.1.100 # Remote's IP address ``` -Options: +### Options -- `-h, --help`: Show the help message and exit. -- `--ip `: Specify the IP Address of the Broadlink device (required). -- `--port `: Set the timeout for device connection in seconds (default: 10). -- `--file `: Specify the output file for saving learned commands (default: commands.json). -- `--read `: Set the time to wait for the user to send the command in seconds (default: 2). +| Name | Required | Default | Description | +|-----------------------|----------|-------------------|---------------------------------------------------------------------| +| `-h, --help` | No | N/A | Show the help message and exit. | +| `--ip ` | Yes | N/A | Specify the IP Address of the Broadlink device. | +| `--port ` | No | `80` | Specify the port of the Broadlink device. | +| `--timeout ` | No | `10` | Set the timeout (in seconds) for device connection. | +| `--type ` | No | `"ir"` | Specify the capture type: `"ir"` or `"rf"`. | +| `--file ` | No | `./commands.json` | Specify the output file for saving learned commands. | +| `--read ` | No | `2` | Set the time to wait (in seconds) for the user to send the command. | -The default input file, [commands.json](./commands.json), can be extended under the commands key to provide the program with additional IR actions. +The default input file, [commands.json](./commands.json), can be extended under the commands key to provide the program +with additional IR actions. ## Contributing diff --git a/main.py b/main.py index 2649684..b21534f 100644 --- a/main.py +++ b/main.py @@ -1,25 +1,25 @@ import argparse import json import os +import remote from broadlink import Device from colorama import Fore, init - -import remote from cli_utils import print_keys init(autoreset=True) data = {} -mFile = 'commands.json' +mFile = './commands.json' read_timeout = 2 +read_type = 'ir' def navigate_actions(device: Device, actions): current_path = [] while True: if current_path: - print(Fore.LIGHTBLUE_EX + f'\nCurrent path: {' > '.join(current_path)}') + print(Fore.LIGHTBLUE_EX + '\nCurrent path: ' + (' > '.join(current_path))) else: print(Fore.LIGHTBLUE_EX + '\nCurrent path: Home') print("Choose an action by index to register:") @@ -49,7 +49,7 @@ def navigate_actions(device: Device, actions): else: # If it's a leaf and read the packet try: - packet = remote.handle_action(device, read_timeout) + packet = remote.read_action(device, read_type, read_timeout) except Exception as e: print(Fore.RED + 'Failed to read from remote: ' + str(e)) continue @@ -67,19 +67,19 @@ def navigate_actions(device: Device, actions): def main(): - global data, mFile, read_timeout + global data, mFile, read_timeout, read_type parser = argparse.ArgumentParser(description='Broadlink Device Command Learning') parser.add_argument('--ip', required=True, help='IP Address of the Broadlink device') parser.add_argument('--port', type=int, default=80, help='Port of the Broadlink device (default: 80)') - parser.add_argument('--timeout', type=int, default=10, help='Timeout for device connection (default: 10)') - parser.add_argument('--file', type=str, default='commands.json', - help='Output file for saving learned commands (default: commands.json)') - parser.add_argument('--read', type=int, default=2, - help='Time to wait for the user to send the command (default: 2 seconds)') + parser.add_argument('--timeout', type=int, default=10, help='Timeout (in seconds) for device connection (default: 10)') + parser.add_argument('--type', type=str, default='ir', choices=['ir', 'rf'], help='Specify the capture type: "ir" or "rf" (default: ir)') + parser.add_argument('--file', type=str, default='./commands.json', help='Output file for saving learned commands (default: ./commands.json)') + parser.add_argument('--read', type=int, default=2, help='Time to wait (in seconds) for the user to send the command (default: 2)') args = parser.parse_args() read_timeout = args.read + read_type = args.type mFile = args.file # Load existing data from the input file if it exists if os.path.exists(mFile): diff --git a/remote.py b/remote.py index f9ec1b8..1206415 100644 --- a/remote.py +++ b/remote.py @@ -7,26 +7,47 @@ def get_device(ip_address: str, port: int, timeout: int) -> Device | None: - print(f'Searching for device in {ip_address}...') + print(f'Connecting to device using IP address: "{ip_address}"...') try: device = broadlink.hello(ip_address=ip_address, port=port, timeout=timeout) print(f'Found device "{device.manufacturer} {device.model} ({device.name})". Attempting to authenticate...') device.auth() return device except Exception as e: - print(Fore.RED + f'Could not find device in {ip_address}.') + print(Fore.RED + f'Could not connect to device using IP address: "{ip_address}", Reason: {e}.') return None -def handle_action(device: Device, read_timeout: int): - print('Reading...') - device.enter_learning() +def read_action(device: Device, type: str, read_timeout: int) -> str: + if type == 'ir': + print('Point to the device and short press the button to be captured...') + device.enter_learning() - # Wait for the user to point the remote - sleep(read_timeout) + # Wait for the user to point the remote + sleep(read_timeout) - print('Getting packets...') - packet = device.check_data() + print('Retrieving packets...') + packet = device.check_data() + elif type == 'rf': + print('Point to the device and long press the button to be captured (step 1)...') + device.sweep_frequency() + + # Wait for the user to point the remote + sleep(read_timeout) + + ok = device.check_frequency() + if not ok: + raise IOError('Failed to acquire frequency') + + print('Point again to the device and short press the button to be captured (step 2)...') + device.find_rf_packet() + + # Wait for the user to point the remote + sleep(read_timeout) + + packet = device.check_data() + else: + raise TypeError('Unknown action type') # Convert bytes to Base64 base64_encoded = base64.b64encode(packet)