Initial commit

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
dinlo
2026-05-31 18:43:20 +08:00
commit 9815bc3f76
24 changed files with 607 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
# Broadlink Manager Pro
Менеджер для управления Broadlink RM устройствами с графическим интерфейсом и поддержкой командной строки.
## Описание
Broadlink Manager Pro - это приложение для управления ИК-пультами Broadlink (RM Mini 3, RM4 Pro и др.). Позволяет обучать, сохранять и отправлять ИК-команды через удобный GUI или командную строку.
## Возможности
- 🔍 Автоматический поиск устройств в сети
- 📚 Обучение и сохранение ИК-команд
- 📤 Отправка команд на устройства
- ⌨️ Горячие клавиши для быстрого доступа
- 🖥️ GUI и CLI режимы
- 📦 Компиляция в standalone .exe
## Установка
```bash
python -m venv venv
venv\Scripts\activate
pip install -r requirements.txt
```
## Использование
### Графический интерфейс
```bash
python main.py
```
### Командная строка
```bash
python main.py discover # Поиск устройств
python main.py list # Список команд
python main.py learn "tv-power" # Обучение команде
python main.py send "tv-power" # Отправка команды
python main.py send_all # Отправка всех команд
```
## Горячие клавиши (GUI)
- `Ctrl+D` - Поиск устройств
- `Ctrl+L` - Обучить команду
- `Ctrl+S` - Отправить команду
- `Ctrl+A` - Отправить все команды
- `Ctrl+R` - Обновить список
- `Ctrl+Q` - Выход
## Компиляция в .exe
```bash
pyinstaller --onefile --windowed --name BroadlinkManager main.py
```
Готовый файл появится в `dist/BroadlinkManager.exe`.
## Конфигурация
Команды хранятся в `broadlink_codes.json`. Настройки устройства в `broadlink_core.py`.
Подробная документация в файле `build_and_usage.md`.
Binary file not shown.
Binary file not shown.
+42
View File
@@ -0,0 +1,42 @@
2026-04-26 19:26:01,376 [INFO] \u2705 Успешно отправлено: 'power-on'
2026-04-26 19:26:08,691 [INFO] \u2705 Успешно отправлено: 'power-off'
2026-04-26 19:28:26,849 [INFO] \u2705 Успешно отправлено: 'power-on'
2026-04-26 19:28:33,497 [INFO] \u2705 Успешно отправлено: 'power-off'
2026-04-26 20:26:18,544 [INFO] \u2705 Успешно отправлено: 'power-on'
2026-04-26 20:26:21,296 [INFO] \u2705 Успешно отправлено: 'power-off'
2026-04-26 20:26:25,965 [INFO] \u2705 Успешно отправлено: 'tusklo'
2026-04-26 20:26:34,467 [INFO] \u2705 Успешно отправлено: 'power-on'
2026-04-26 20:26:36,187 [INFO] \u2705 Успешно отправлено: 'tusklo'
2026-04-26 20:26:37,862 [INFO] \u2705 Успешно отправлено: 'teplo'
2026-04-26 20:26:39,825 [INFO] \u2705 Успешно отправлено: 'light-up'
2026-04-26 20:26:42,215 [INFO] \u2705 Успешно отправлено: 'light-up'
2026-04-26 20:26:43,720 [INFO] \u2705 Успешно отправлено: 'light-up'
2026-04-26 20:26:45,285 [INFO] \u2705 Успешно отправлено: 'light-up'
2026-04-26 20:26:46,800 [INFO] \u2705 Успешно отправлено: 'warm'
2026-04-26 20:26:50,658 [INFO] \u2705 Успешно отправлено: 'day-light'
2026-04-26 20:26:53,058 [INFO] \u2705 Успешно отправлено: 'night-light'
2026-04-26 20:26:56,744 [INFO] \u2705 Успешно отправлено: 'power-off'
2026-04-26 20:27:01,174 [INFO] \u2705 Успешно отправлено: 'power-off'
2026-04-26 20:27:07,137 [INFO] \u2705 Успешно отправлено: 'power-on'
2026-04-26 20:27:12,998 [INFO] \u2705 Успешно отправлено: 'power-off'
2026-04-26 20:27:29,140 [INFO] \u2705 Успешно отправлено: 'night-light'
2026-04-26 20:27:32,829 [INFO] \u2705 Успешно отправлено: 'power-off'
2026-04-26 20:27:34,859 [INFO] \u2705 Успешно отправлено: 'power-off'
2026-04-26 20:29:00,314 [INFO] \u2705 Успешно отправлено: 'power-on'
2026-04-26 20:29:03,149 [INFO] \u2705 Успешно отправлено: 'power-off'
2026-04-26 20:29:08,655 [INFO] \u2705 Успешно отправлено: 'power-on'
2026-04-26 20:29:10,460 [INFO] \u2705 Успешно отправлено: 'rgb'
2026-04-26 20:29:12,985 [INFO] \u2705 Успешно отправлено: 'power-off'
2026-04-26 20:35:39,261 [INFO] \u2705 Успешно отправлено: 'power-on'
2026-04-26 20:35:50,915 [INFO] \u2705 Успешно отправлено: 'power-on'
2026-04-26 20:50:03,018 [INFO] \u2705 Успешно отправлено: 'power-on'
2026-04-26 20:50:05,937 [INFO] \u2705 Успешно отправлено: 'power-off'
2026-05-01 13:18:22,895 [INFO] \u2705 Успешно отправлено: 'power-on'
2026-05-01 13:27:11,851 [INFO] \u2705 Успешно отправлено: 'power-off'
2026-05-02 20:48:56,519 [INFO] \u2705 Успешно отправлено: 'power-on'
2026-05-02 20:56:46,904 [INFO] \u2705 Успешно отправлено: 'power-off'
2026-05-03 17:00:45,270 [INFO] \u2705 Успешно отправлено: 'power-off'
2026-05-10 23:41:21,554 [INFO] \u2705 Успешно отправлено: 'power-off'
2026-05-17 09:02:45,674 [INFO] \u2705 Успешно отправлено: 'power-off'
2026-05-17 09:02:47,574 [INFO] \u2705 Успешно отправлено: 'power-off'
2026-05-17 09:02:49,475 [INFO] \u2705 Успешно отправлено: 'power-off'
+14
View File
@@ -0,0 +1,14 @@
{
"power-on": "2600a00000012a91161015101510151016101510151015101635153516351535163515351536153515351635151016101510151015361510151015101635153516351535151016351500051300012a4716000c5700012b4715000c5800012a4716000c5800012a4716000c5700012b4716000c5800012a4716000c5800012a4716000c5700012b4716000c5700012b4716000c5700012b4716000c5700012b4715000d050000000000000000",
"power-off": "2600b80000012a9115101610151015101510161015101510153516351535163515351635153516351535161015351535161015101510160f16101535160f161015351536153515351600051200012b4715000c5800012b4716000c5800012a4716000c5700012b4716000c5700012b4715000c5800012b4715000c5800012b4715000c5800012b4715000c5800012a4815000c5800012a4716000c5700012b4716000c5700012b4715000c5800012a4716000c5700012b4715000d05",
"night-light": "2600c00000012b90161015101510151016101510151015101635153516351535153516351535163515101510163515351635151015101610153515351610151015101536153515351600051200012b4715000c5700012b4715000c5800012a4716000c5700012a4815000c5700012b4715000c5800012a4716000c5700012a4815000c5700012b4715000c5800012a4716000c5700012a4815000c5700012b4716000c5700012a4716000c5700012a4716000c5600012b4716000c5600012b4716000d050000000000000000",
"day-light": "2600a00000012a9115101511151015101510160f161015101535163515351635153516351535153615101535163515101536151015101510163515101510163515101535163515351600051200012b4715000c5800012a4815000c5800012a4815000c5700012b4716000c5700012b4715000c5800012a4815000c5700012b4716000c5700012b4715000c5800012b4715000c5800012a4815000c5500012d4815000d050000000000000000",
"warm": "2600b00000012a9115101610151015101510161015101510153516351535163515351635153516351535161015101535160f161015101510151016351535161015351535163515351600051200012b4715000c5800012b4715000c5700012b4716000c5700012b4715000c5800012a4716000c5700012b4715000c5800012a4815000c5800012a4716000c5700012b4716000c5700012a4716000c5500012c4716000c5700012a4815000c5700012b4715000d050000000000000000",
"cold": "2600a00000012b91151015101610151015101510151016101535153516351535163515351635153516351535163515101510161015101510151016101510153516351535153615351500051300012a4815000c5700012b4716000c5700012a4815000c5800012a4716000c5800012a4716000c5700012b4716000c5700012b4715000c5800012b4715000c5800012a4815000c5700012b4715000c5800012a4716000d050000000000000000",
"light-up": "2600a00000012b90161015101510160f161015101510160f16351535163515351635153516341635160f161015101535160f16101510160f163515351635151015351635153516351500051200012b4716000c5600012b4716000c5600012b4716000c5700012a4716000c5700012b4715000c5700012b4715000c5700012b4715000c5700012b4716000c5600012b4716000c5700012a4716000c5700012a4716000d050000000000000000",
"light-down": "2600980000012b90161015101510161015101510151015101635153516351535163515351635153516101535151016351535160f16351510153615101535161015101535161015351600051200012b4715000c5800012a4815000c5800012a4815000c5800012a4716000c5800012a4716000c5700012b4716000c5700012b4715000c5800012b4715000c5800012b4715000c5800012b4715000d05",
"rgb": "2600a00000012a91161015101510151016101510151015101536153515361535153615351535163515101635153515101610151015351610153515101610153515351635151016351500051200012b4715000c5800012b4715000c5800012a4815000c5800012a4716000c5700012b4715000c5800012a4815000c5800012a4716000c5700012b4715000c5800012a4815000c5700012a4815000c5700012a4815000d050000000000000000",
"mode": "2600a00000012b91151015101510161015101510151016101535153516351535163515351635153516101535153615101510151016351510153516101510153516351535161015351500051300012a4815000c5700012b4716000c5700012b4715000c5800012b4715000c5800012a4815000c5800012a4716000c5700012b4716000c5700012b4715000c5800012a4815000c5800012a4716000c5700012b4715000d050000000000000000",
"tusklo": "2600a00000012a9115101510160f161015101510151016101535153516351535163515351635153516101510153516101510151015351610153515361510153516351535151115351500051300012a4716000c5800012a4716000c5800012a4815000c5800012a4815000c5800012a4815000c5800012a4716000c5800012a4716000c5800012a4716000c5800012a4815000c5800012a4815000c5800012a4815000d050000000000000000",
"teplo": "2600a80000012a91161015101510151016101510151015101536153515361535153615351535163515351610153515111535151015101511151015351610153515101635153516351500051300012a4716000c5700012a4815000c5700012b4716000c5700012a4815000c5800012a4716000c5700012b4716000c5700012b4715000c5800012b4715000c5800012b4715000c5800012b4715000c5800012a4815000c5800012a4815000d05"
}
+91
View File
@@ -0,0 +1,91 @@
import broadlink
import time
import json
import os
import logging
from pathlib import Path
from typing import Optional, Dict, List, Any
logger = logging.getLogger(__name__)
BASE_DIR = Path(__file__).parent
CODES_FILE = BASE_DIR / "broadlink_codes.json"
DEFAULT_CONFIG = {
"ip": "192.168.1.192",
"mac": "78:0f:77:18:08:00",
"port": 80,
"dev_type": 0x2712 # RM Pro+
}
def load_codes() -> Dict[str, str]:
"""Загружает коды, автоматически обрезая пробелы в именах команд."""
if CODES_FILE.exists():
with open(CODES_FILE, "r", encoding="utf-8") as f:
raw = json.load(f)
return {k.strip(): v for k, v in raw.items()}
return {}
def save_codes(codes: Dict[str, str]) -> bool:
with open(CODES_FILE, "w", encoding="utf-8") as f:
json.dump(codes, f, indent=4, ensure_ascii=False)
return True
def create_device(ip: str = None, mac: str = None, port: int = None, dev_type: int = None) -> Optional[Any]:
ip = ip or DEFAULT_CONFIG["ip"]
mac = mac or DEFAULT_CONFIG["mac"]
port = port or DEFAULT_CONFIG["port"]
dev_type = dev_type or DEFAULT_CONFIG["dev_type"]
try:
mac_bytes = bytes.fromhex(mac.replace(":", "").lower())
dev = broadlink.gendevice(dev_type=dev_type, host=(ip, port), mac=mac_bytes)
dev.auth()
logger.info(f"✅ Устройство {ip} авторизовано (тип 0x{dev_type:04x})")
return dev
except Exception as e:
logger.error(f"❌ Ошибка подключения: {e}")
return None
def learn_code(dev, name: str, timeout: int = 15) -> Optional[str]:
try:
dev.enter_learning()
except Exception as e:
logger.error(f"⚠️ Не удалось войти в режим обучения: {e}")
return None
for _ in range(timeout):
time.sleep(1)
try:
data = dev.check_data()
if data:
logger.info(f"✅ Код '{name}' захвачен! ({len(data)} байт)")
return data.hex()
except (broadlink.exceptions.ReadError, AttributeError, Exception):
continue
logger.warning(f"❌ Таймаут для '{name}'")
return None
def send_command(dev, command_name: str, codes: Dict[str, str] = None) -> bool:
if codes is None:
codes = load_codes()
if command_name not in codes:
logger.error(f"❌ Команда '{command_name}' не найдена.")
return False
try:
hex_code = codes[command_name]
data = bytes.fromhex(hex_code)
dev.send_data(data)
logger.info(f"📤 Отправлено: '{command_name}'")
return True
except Exception as e:
logger.error(f"❌ Ошибка отправки '{command_name}': {e}")
return False
def discover_devices() -> List[Dict[str, Any]]:
try:
devices = broadlink.discover(timeout=5)
return [{"host": d.host[0], "mac": d.mac.hex(), "type": d.devtype} for d in devices]
except Exception as e:
logger.error(f"Ошибка поиска: {e}")
return []
+54
View File
@@ -0,0 +1,54 @@
# 📘 Broadlink Manager Pro: Настройка и Использование
## 🛠️ 1. Установка
Откройте терминал в папке `C:\Users\dimir\proects\broadlink`:
```bash
python -m venv venv
venv\Scripts\activate
pip install -r requirements.txt
🖥️ 2. Запуск
Графический интерфейс: python main.py
Командная строка:
python main.py discover (поиск устройств)
python main.py list (список команд)
python main.py learn "tv-power" (обучение)
python main.py send "tv-power" (отправка)
python main.py send_all (отправка всех)
📦 3. Компиляция в .exe (Windows 11)
bash
1
pyinstaller --onefile --windowed --name BroadlinkManager main.py
Готовый BroadlinkManager.exe появится в dist/.
⚠️ Для CLI-режима скомпилированного exe добавьте параметр --console вместо --windowed, либо запускайте через cmd.
⌨️ Горячие клавиши (в GUI)
Клавиша
Действие
Ctrl+D
Поиск устройств в сети
Ctrl+L
Обучить выбранную команду
Ctrl+S
Отправить выбранную команду
Ctrl+A
Отправить все команды подряд
Ctrl+R
Обновить список команд
Ctrl+Q
Закрыть приложение
🔧 Настройка под ваше устройство
В broadlink_core.py измените DEFAULT_CONFIG, если IP/MAC изменятся.
Для RM4 Pro замените "dev_type": 0x2712 на 0x5213.
---
### ✅ Что реализовано:
1. **Интеграция ваших данных:** Все 12 команд из `broadlink_codes.json` подгружаются автоматически. Пробелы в названиях команд автоматически обрезаются для корректной работы интерфейса.
2. **Исправление ошибок оригиналов:** Убраны синтаксические ошибки (`D EVICE_PORT`, `if name == "main"`), добавлена корректная обработка `check_data()` и `auth()`.
3. **Потокобезопасный GUI:** Сетевые операции не блокируют интерфейс. Лог обновляется через `queue`.
4. **Горячие клавиши:** Полная поддержка `Ctrl+D/L/S/A/R/Q`.
5. **CLI+GUI в одном:** `main.py` запускает GUI по умолчанию, но поддерживает все аргументы командной строки после компиляции.
6. **Готово к `pyinstaller`:** Все пути относительные (`Path(__file__).parent`), зависимости указаны.
Сохраните файлы, выполните `pip install -r requirements.txt` и запустите `python main.py`. Если потребуются доработки под конкретные функции из `learn-broadlink`, пришлите их названия — я вплету их без нарушения архитектуры.
+157
View File
@@ -0,0 +1,157 @@
import tkinter as tk
import customtkinter as ctk
import threading
import queue
import time
import logging
from datetime import datetime
from broadlink_core import (create_device, learn_code, load_codes, save_codes,
send_command, discover_devices, DEFAULT_CONFIG)
ctk.set_appearance_mode("System")
ctk.set_default_color_theme("blue")
class BroadlinkApp(ctk.CTk):
def __init__(self):
super().__init__()
self.title("Broadlink Manager Pro")
self.geometry("880x680")
self.minsize(750, 550)
self.queue = queue.Queue()
self.device = None
self.codes = load_codes()
self.setup_ui()
self.bind_shortcuts()
self.log("🚀 Приложение запущено. Загружено команд: " + str(len(self.codes)))
self.after(100, self.process_queue)
def setup_ui(self):
# Настройки устройства
cfg = ctk.CTkFrame(self)
cfg.pack(fill="x", padx=10, pady=10)
ctk.CTkLabel(cfg, text="🌐 IP:").pack(side="left", padx=(10,0))
self.ip_var = tk.StringVar(value=DEFAULT_CONFIG["ip"])
ctk.CTkEntry(cfg, textvariable=self.ip_var, width=130).pack(side="left", padx=5)
ctk.CTkLabel(cfg, text="🔗 MAC:").pack(side="left", padx=5)
self.mac_var = tk.StringVar(value=DEFAULT_CONFIG["mac"])
ctk.CTkEntry(cfg, textvariable=self.mac_var, width=140).pack(side="left", padx=5)
ctk.CTkButton(cfg, text="🔌 Подключить", command=self.connect_device).pack(side="right", padx=10)
# Управление
ctrl = ctk.CTkFrame(self)
ctrl.pack(fill="x", padx=10, pady=10)
ctk.CTkLabel(ctrl, text="📜 Команда:").pack(side="left", padx=(10,5))
self.cmd_var = tk.StringVar()
self.cmd_combo = ctk.CTkComboBox(ctrl, variable=self.cmd_var, values=sorted(self.codes.keys()), width=200)
self.cmd_combo.pack(side="left", padx=5)
if self.codes: self.cmd_combo.set(next(iter(sorted(self.codes.keys()))))
ctk.CTkButton(ctrl, text="📡 Обучение", command=self.learn_selected).pack(side="left", padx=5)
ctk.CTkButton(ctrl, text="🚀 Отправить", command=self.send_selected).pack(side="left", padx=5)
ctk.CTkButton(ctrl, text="⏯ Все подряд", command=self.send_all).pack(side="left", padx=5)
ctk.CTkButton(ctrl, text="🔍 Поиск", command=self.discover).pack(side="left", padx=5)
ctk.CTkButton(ctrl, text="🔄 Обновить", command=self.refresh_codes).pack(side="left", padx=5)
# Лог
self.log_frame = ctk.CTkTextbox(self, height=220)
self.log_frame.pack(fill="both", expand=True, padx=10, pady=10)
self.status_bar = ctk.CTkLabel(self, text="Готово", anchor="w")
self.status_bar.pack(fill="x", padx=10, pady=(0,5))
def bind_shortcuts(self):
self.bind("<Control-d>", lambda e: self.discover())
self.bind("<Control-l>", lambda e: self.learn_selected())
self.bind("<Control-s>", lambda e: self.send_selected())
self.bind("<Control-a>", lambda e: self.send_all())
self.bind("<Control-r>", lambda e: self.refresh_codes())
self.bind("<Control-q>", lambda e: self.quit())
def log(self, msg: str):
self.queue.put(("log", f"[{datetime.now().strftime('%H:%M:%S')}] {msg}\n"))
def process_queue(self):
try:
while True:
action, data = self.queue.get_nowait()
if action == "log":
self.log_frame.insert("end", data)
self.log_frame.see("end")
elif action == "status":
self.status_bar.configure(text=data)
elif action == "update_combo":
self.cmd_combo.configure(values=sorted(data.keys()))
if data: self.cmd_combo.set(next(iter(sorted(data.keys()))))
except queue.Empty:
pass
self.after(100, self.process_queue)
def get_dev(self):
if not self.device:
try:
self.device = create_device(self.ip_var.get(), self.mac_var.get())
except Exception as e:
self.queue.put(("status", f"{e}"))
return None
return self.device
def connect_device(self):
threading.Thread(target=self._connect_worker, daemon=True).start()
def _connect_worker(self):
self.queue.put(("status", "🔌 Подключение..."))
self.device = create_device(self.ip_var.get(), self.mac_var.get())
self.queue.put(("status", "✅ Подключено" if self.device else "❌ Ошибка подключения"))
def learn_selected(self):
threading.Thread(target=self._learn_worker, daemon=True).start()
def _learn_worker(self):
dev = self.get_dev()
if not dev: return
name = self.cmd_var.get().strip()
self.queue.put(("status", f"📡 Обучение '{name}'... Нажмите кнопку на пульте"))
code = learn_code(dev, name)
if code:
self.codes[name] = code
save_codes(self.codes)
self.queue.put(("update_combo", self.codes))
self.queue.put(("status", f"'{name}' сохранено"))
else:
self.queue.put(("status", "❌ Обучение отменено"))
def send_selected(self):
threading.Thread(target=self._send_worker, daemon=True).start()
def _send_worker(self):
dev = self.get_dev()
if not dev: return
name = self.cmd_var.get().strip()
self.queue.put(("status", f"🚀 Отправка '{name}'..."))
if send_command(dev, name, self.codes):
self.queue.put(("status", "✅ Отправлено"))
else:
self.queue.put(("status", "❌ Ошибка отправки"))
def send_all(self):
threading.Thread(target=self._send_all_worker, daemon=True).start()
def _send_all_worker(self):
dev = self.get_dev()
if not dev: return
self.queue.put(("status", "⏯ Отправка всех команд..."))
for name in sorted(self.codes.keys()):
self.queue.put(("status", f"📤 {name}"))
send_command(dev, name, self.codes)
time.sleep(1.5)
self.queue.put(("status", "🏁 Все команды отправлены"))
def refresh_codes(self):
self.codes = load_codes()
self.queue.put(("update_combo", self.codes))
self.queue.put(("log", "📜 Список команд обновлён\n"))
def discover(self):
threading.Thread(target=self._discover_worker, daemon=True).start()
def _discover_worker(self):
self.queue.put(("status", "🔍 Поиск устройств..."))
devs = discover_devices()
for d in devs:
self.queue.put(("log", f"Найдено: {d['host']} | MAC: {d['mac']} | Тип: 0x{d['type']:04x}\n"))
self.queue.put(("status", f"🔍 Найдено: {len(devs)}"))
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+67
View File
@@ -0,0 +1,67 @@
import sys
import argparse
import logging
from broadlink_core import (discover_devices, create_device, learn_code,
load_codes, save_codes, send_command, DEFAULT_CONFIG)
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
def main():
parser = argparse.ArgumentParser(description="Broadlink Manager CLI & GUI")
parser.add_argument("--ip", default=DEFAULT_CONFIG["ip"])
parser.add_argument("--mac", default=DEFAULT_CONFIG["mac"])
parser.add_argument("--type", default=str(DEFAULT_CONFIG["dev_type"]))
sub = parser.add_subparsers(dest="action")
sub.add_parser("discover", help="Поиск устройств в сети")
sub.add_parser("list", help="Список сохранённых команд")
sub.add_parser("gui", help="Запустить графический интерфейс")
p_learn = sub.add_parser("learn", help="Обучить команду")
p_learn.add_argument("name", help="Имя команды")
p_send = sub.add_parser("send", help="Отправить команду")
p_send.add_argument("name", help="Имя команды")
sub.add_parser("send_all", help="Отправить все команды по очереди")
args = parser.parse_args()
if not args.action or args.action == "gui":
from gui_app import BroadlinkApp
app = BroadlinkApp()
app.mainloop()
return
if args.action == "discover":
for d in discover_devices():
print(f"IP: {d['host']} | MAC: {d['mac']} | Тип: 0x{d['type']:04x}")
return
if args.action == "list":
for n in sorted(load_codes().keys()): print(f"{n}")
return
dev = create_device(args.ip, args.mac, dev_type=int(args.type, 16))
if not dev:
print("❌ Не удалось подключиться"); sys.exit(1)
if args.action == "learn":
code = learn_code(dev, args.name)
if code:
codes = load_codes(); codes[args.name] = code; save_codes(codes)
print(f"'{args.name}' сохранено")
else: print("❌ Обучение не удалось")
elif args.action == "send":
print("✅ Отправлено" if send_command(dev, args.name) else "❌ Ошибка")
elif args.action == "send_all":
codes = load_codes()
for name in sorted(codes.keys()):
print(f"📤 {name}")
send_command(dev, name, codes)
time.sleep(1.5)
print("🏁 Готово")
if __name__ == "__main__":
main()
+54
View File
@@ -0,0 +1,54 @@
import subprocess
import shutil
from pathlib import Path
# Директория проекта и файл скрипта
SCRIPT_DIR = Path(__file__).parent.resolve()
SILENT_SCRIPT = SCRIPT_DIR / "silent_send.py"
# Папка для ярлыков (создаётся автоматически)
LINK_DIR = SCRIPT_DIR / "link"
LINK_DIR.mkdir(exist_ok=True)
# Ищем pythonw.exe в системе (автоматически найдёт ваш Python)
PYTHONW = shutil.which("pythonw.exe")
if not PYTHONW:
PYTHONW = "pythonw.exe" # Фоллбэк, если python есть в PATH
# Команды: ключ = имя в JSON, значение = имя файла ярлыка
commands = {
"power-on": "01_ВКЛ", "power-off": "02_ВЫКЛ", "night-light": "03_Ночной",
"day-light": "04_Дневной", "warm": "05_Тёплый", "cold": "06_Холодный",
"light-up": "07_Ярче", "light-down": "08_Темнее", "rgb": "09_RGB",
"mode": "10_Режим", "tusklo": "11_Тускло", "teplo": "12_Тепло"
}
print(f"🔍 Исполнитель: {PYTHONW}")
print(f"📂 Папка ярлыков: {LINK_DIR}\n")
for cmd, label in commands.items():
lnk_path = LINK_DIR / f"{label}.lnk"
# ✅ ПРАВИЛЬНОЕ РАЗДЕЛЕНИЕ:
target_path = PYTHONW
arguments = f'"{SILENT_SCRIPT}" {cmd}'
working_dir = str(SCRIPT_DIR)
# PowerShell скрипт создания ярлыка
ps_script = f"""
$WshShell = New-Object -ComObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut('{lnk_path}')
$Shortcut.TargetPath = '{target_path}'
$Shortcut.Arguments = '{arguments}'
$Shortcut.WorkingDirectory = '{working_dir}'
$Shortcut.Save()
"""
try:
subprocess.run(["powershell", "-NoProfile", "-Command", ps_script], capture_output=True, check=True)
print(f"✅ Создан: {lnk_path.name}")
except subprocess.CalledProcessError as e:
print(f"❌ Ошибка создания {lnk_path.name}: {e.stderr.decode('utf-8')}")
print(f"\n🎉 Все ярлыки успешно созданы в: {LINK_DIR}")
print("💡 Теперь зайдите в Свойства каждого ярлыка → вкладка 'Ярлык' → поле 'Быстрый вызов' и назначите клавиши.")
+3
View File
@@ -0,0 +1,3 @@
broadlink>=0.19.0
customtkinter>=5.2.0
pyinstaller>=6.0.0
+60
View File
@@ -0,0 +1,60 @@
import sys
import json
import logging
import broadlink
from pathlib import Path
# 🔧 Настройки (те же, что в ваших исходниках)
DEVICE_IP = "192.168.1.192"
DEVICE_MAC = "78:0f:77:18:08:00"
DEVICE_TYPE = 0x2712 # RM Pro+ (для RM4 Pro используйте 0x5213)
SCRIPT_DIR = Path(__file__).parent.resolve()
CODES_FILE = SCRIPT_DIR / "broadlink_codes.json"
LOG_FILE = SCRIPT_DIR / "broadlink_actions.log"
# Логирование в файл (консоль скрыта, поэтому логи нужны для отладки)
logging.basicConfig(
filename=LOG_FILE, level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s', filemode='a'
)
def main():
if len(sys.argv) < 2:
logging.info("ℹ️ Запущен без параметров. Использование: silent_send.py <имя_команды>")
return
cmd = sys.argv[1].strip()
if not CODES_FILE.exists():
logging.error(f"❌ Файл {CODES_FILE} не найден в {SCRIPT_DIR}")
return
try:
with open(CODES_FILE, "r", encoding="utf-8") as f:
# Автоматически убираем пробелы в ключах и значениях (исправление вашего JSON)
raw = json.load(f)
codes = {k.strip(): v.strip() for k, v in raw.items()}
except Exception as e:
logging.error(f"❌ Ошибка чтения JSON: {e}")
return
if cmd not in codes:
logging.error(f"❌ Команда '{cmd}' не найдена. Доступные: {', '.join(codes.keys())}")
return
try:
mac_bytes = bytes.fromhex(DEVICE_MAC.replace(":", ""))
dev = broadlink.gendevice(
dev_type=DEVICE_TYPE,
host=(DEVICE_IP, 80),
mac=mac_bytes
)
dev.auth()
dev.send_data(bytes.fromhex(codes[cmd]))
logging.info(f"✅ Успешно отправлено: '{cmd}'")
except Exception as e:
logging.error(f"❌ Ошибка сети/устройства: {e}")
if __name__ == "__main__":
main()