From 9815bc3f76c231ee796dd77ede505c35bf9865c5 Mon Sep 17 00:00:00 2001 From: dinlo Date: Sun, 31 May 2026 18:43:20 +0800 Subject: [PATCH] Initial commit Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 65 +++++++++ __pycache__/broadlink_core.cpython-312.pyc | Bin 0 -> 5923 bytes __pycache__/gui_app.cpython-312.pyc | Bin 0 -> 15035 bytes broadlink_actions.log | 42 ++++++ broadlink_codes.json | 14 ++ broadlink_core.py | 91 ++++++++++++ build_and_usage.md | 54 +++++++ gui_app.py | 157 +++++++++++++++++++++ link/01_ВКЛ.lnk | Bin 0 -> 1481 bytes link/02_ВЫКЛ.lnk | Bin 0 -> 1483 bytes link/03_Ночной.lnk | Bin 0 -> 1487 bytes link/04_Дневной.lnk | Bin 0 -> 1483 bytes link/05_Тёплый.lnk | Bin 0 -> 1473 bytes link/06_Холодный.lnk | Bin 0 -> 1473 bytes link/07_Ярче.lnk | Bin 0 -> 1481 bytes link/08_Темнее.lnk | Bin 0 -> 1485 bytes link/09_RGB.lnk | Bin 0 -> 1471 bytes link/10_Режим.lnk | Bin 0 -> 1473 bytes link/11_Тускло.lnk | Bin 0 -> 1477 bytes link/12_Тепло.lnk | Bin 0 -> 1475 bytes main.py | 67 +++++++++ make_shortcuts.py | 54 +++++++ requirements.txt | 3 + silent_send.py | 60 ++++++++ 24 files changed, 607 insertions(+) create mode 100644 README.md create mode 100644 __pycache__/broadlink_core.cpython-312.pyc create mode 100644 __pycache__/gui_app.cpython-312.pyc create mode 100644 broadlink_actions.log create mode 100644 broadlink_codes.json create mode 100644 broadlink_core.py create mode 100644 build_and_usage.md create mode 100644 gui_app.py create mode 100644 link/01_ВКЛ.lnk create mode 100644 link/02_ВЫКЛ.lnk create mode 100644 link/03_Ночной.lnk create mode 100644 link/04_Дневной.lnk create mode 100644 link/05_Тёплый.lnk create mode 100644 link/06_Холодный.lnk create mode 100644 link/07_Ярче.lnk create mode 100644 link/08_Темнее.lnk create mode 100644 link/09_RGB.lnk create mode 100644 link/10_Режим.lnk create mode 100644 link/11_Тускло.lnk create mode 100644 link/12_Тепло.lnk create mode 100644 main.py create mode 100644 make_shortcuts.py create mode 100644 requirements.txt create mode 100644 silent_send.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..1186ce3 --- /dev/null +++ b/README.md @@ -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`. diff --git a/__pycache__/broadlink_core.cpython-312.pyc b/__pycache__/broadlink_core.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf48b1a07d70a550a13ab8eb74af3ad2f4bbeb5b GIT binary patch literal 5923 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 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 [] \ No newline at end of file diff --git a/build_and_usage.md b/build_and_usage.md new file mode 100644 index 0000000..4da67cf --- /dev/null +++ b/build_and_usage.md @@ -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`, пришлите их названия — я вплету их без нарушения архитектуры. \ No newline at end of file diff --git a/gui_app.py b/gui_app.py new file mode 100644 index 0000000..6b46a19 --- /dev/null +++ b/gui_app.py @@ -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("", lambda e: self.discover()) + self.bind("", lambda e: self.learn_selected()) + self.bind("", lambda e: self.send_selected()) + self.bind("", lambda e: self.send_all()) + self.bind("", lambda e: self.refresh_codes()) + self.bind("", 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)}")) \ No newline at end of file diff --git a/link/01_ВКЛ.lnk b/link/01_ВКЛ.lnk new file mode 100644 index 0000000000000000000000000000000000000000..b30c66b9dde2223720afbf140b793ffd3e76189b GIT binary patch literal 1481 zcmbu8TS!z<6o%KR-6+V)EYwhfEGP$Oro7R`amHz?p@!7(veS+_jf0MJI!=s+R3rsP z(L-rA1o;ryLlKhnP!du}2x>;zOM=u}5h+AHbpO}xm@^gh&_1lSFKh2_uXEOaTts9@ zh=C^>ke(cO(Wmja^TO8?g)aT4K*fW2i@A)hHgyH4X^59w!9lZx8Lg*9d1O+LDY>pyFBAFzu zwBmg2QnIS5T?r~!2_}_Qc1}KZp7pM<^uJqoXK>`xio-p8 zE-Z4Yj5zcDAmd9&`mc8IK`;<1y0dzoH9ZvEb{FuCH%6{t}fXmb=ZS1|L#Uzw)M zm&io{ssXJ5yAr7U7jHdyZY|)|6n_)nG?V8we^`v{XJ*$zIGW2YqG_fL$Vvm%3$Ku$ zaGX|Bu}*HO=q(4iC0G||B-*NTc{MDKS2I@|Sp>f%g2}@zpD%*jf;X=N8%HUTgw8xH z#;jnL6+8FrK8ixvqo~OWQ$vy(QFw;Zg&SHF)dHd!>no`mJ!__|tiYQ_s?0ZJ^fY_O zsna_8!Q)U(l+ZuCBvTbO3$@Q!cU60+3 z5B?P&1!n>H?dVNK?xI(xOC9~L9NXP5%g(2F`A7X&ml90HfVETvZ-Q-tndQhrnUqUh zs!Ti^DNDw(FwVw$HfCnZK|BKy3uZZ(XTzHiWfq^p52m+V^qfd<{PhW gX+Evg=x39D>7^QQHqp|B&vG95ohaBjca4}}j)KmY&$ literal 0 HcmV?d00001 diff --git a/link/02_ВЫКЛ.lnk b/link/02_ВЫКЛ.lnk new file mode 100644 index 0000000000000000000000000000000000000000..934630cf77d777f311395a8df4937f84012f53f2 GIT binary patch literal 1483 zcmbu8T}V_>5XWat`ye4LGciLEvY_1D-SVp}bywSJNl6VqP9Im@)DIb%tFNvn^MPSd;;lY=0ZIgyKQ>(ojK-}W)kmM+8BQM2~HNPl7noN>b zTG3s-l5$klrg#;scw@^BUwr+{-sM_j8Tz>K&dB)J*H>K!b2@g9JgObOeJY!lQ7G== zbAFM2#E3JWj&Y8QByYjo*BU8@R6I6Pa4*wysZhVyFHDXIax_#ab+jXhnIV|@`L9gV zmCNLy2C4$B4!feLe4e)nyh5$Pr74~!zG*Jc8y>$HKg7)5=qem*u(=NR|2ejLv2! z*>yTc{35T4=^+!Gb13w8Q3G=Jk&&F#3?+(%HbUc|TyGwep)4wt-=BNn!>Wzp+=XP# z6eUegtw~h7J#J0tJtkz$#ayMt?1$9{i^(hkGe>I}@rWQB+*Bf)r3Gx|4c66CSL~CX9!}-Bbi?f^LJFrDvfO z${;RQ3ZAW$Dr2ee)3Ba~nVHfNPe#OoSvuxvuqH&A#i#I#>5_qw6B&s1*!sIv`*nM^ i@mf}M=Q^y{@E$w#aC_(b)-Q&5XWcLKBOQkGf_hcGND}D-SUg9bXVJINl7iuQ+CyDU3B$!-B3d+k^&>? zq5Nox@*%K?dZ?rzC8UxN(uzJ_5+vV>P(kdW59>e2tL{?JLvxuqbIzReJ9F>Mbr6vu zJP4MkUs}rP#Tdta`<0((vp*OP-fkVKe>Y;7o2wlLj?%1^t~4iu(kXWLuBhN*ZAZ7Q zWuUNarYdPX9{b{H;&B-EEbZ@kKV};-e4JS0-3a2t>-Qu%oX(P$V#u14pA$hQNh>P3 zT(OEWRn?|=6|8upijTC9zOlEu)>`_;6CMo=eH*>uI*{42Yv6fR|AW&zDU?ER51;di z)KVkPOdaDKgC%dlJkgF*CaHLAq~KnrXHlMhuTPj9VdSVSSE^}S05eT6bNa7L)3vMQ zpjs*its1++>CilH6L@)AtxHopb$ruYp0_+cF?5)joy%ZoF&jV8Oq-FF2C55IArIj= zMN)xIZY;Tb2;@d@Wo-@7cAd+sK5?dux!TZj*p+@vb~CSuLK)MDT0K~JRx3X zf>~T(&)s(lg|G!sqZOvsOX?Cv`N@?#S^(7oq8af>D#OS<(_Eb8iX&C#TW@qWILWTl zIpP!9`RqA2KbXbhC=&0`{zMTPSFa}Ru3ov@s{kgVyV zu+FK~i3+#JtqHxygsi!ktCX1iu=-#znT3DmXbmGC5nuzGO87N|_@j`#vQUD++3cqi zNPc0*!VAZdmC|u@A=<%-#;Ai|L2k5%8#6w|E#pKhdS$Fk&K2yb#*P}Qq6}L9-wXk^ z8$>6FUMiKj)Zh-I%z8b=Vcm_!twc0Y-aPAb0TvN;{eq;!=VGDzyP8%EPZ@T<*mK_g z?(N=?iAx+i3Ps_%iZHUMO1Veje$^#w@I}nCPp@;&wN3W)?7u&=!5kEA9h_WwJ9)bE zWnRtGf5nHv2?f6eqbWZxf9yh0PM;&y+WTYm<@ip|um|h15o6(SN9Dtspj)A4=~*a= z(ufO|gl7vS%UCk}6s)IUW~Nlc6A`gsmWp`_tO-$O@hSXfx};&`MD9j-Y-n^o4y*d+Y&94VT%OkHZQ*c0ozQk literal 0 HcmV?d00001 diff --git a/link/04_Дневной.lnk b/link/04_Дневной.lnk new file mode 100644 index 0000000000000000000000000000000000000000..5bd9e465974faf429675dc740e43fd73beea5bea GIT binary patch literal 1483 zcmbu8T}V_>5XWcLzEH^0EYwhhOehz3xBTi$-PN{QQZmDjlXlf@U3B$&-B2Sck^-aX zp)6a1dB#bA8H z7IplrO_@r>zC@aS6plU12m0Pj+Qtp)q}$y+p!wMNP&6_1S++{^SFD$wus36mq791T@U9c>R_W(a0}{wvdT<2pI0 zfvP~O!>&jwUEpm3uRv>XX^N+bZ<^2Zw#O$%4l}be6o!_v@e|Fo8Chwdx?mOZ5RTJ2 zI;fLd%X&*eZuQnQ)D!L0xxDHVrz@GOjfBCj@?)~Q=qemE^eMNtO9F8=Wmqvg>q? z_(X0M(}PAi=MX>gysP@<+)PGtQVWzQ8rlerg>t=lOop=tQ?&+>>1vB&OY{f zU&!<&jvaxbXkEn^SyZLUqj0b4k~1_M`}D)h{IeZ1eSHV+{@h>=im?vOth$r>x$}8J z{gZ#iN5NSEemh1}VL{>K`Qp5Xj&y7P_lV1hot{w-)@2jM!QpNygf&68Le0{%Pzq%b z7b^wN7D|<|RQPFFPs7Yi>4+yIV!Uu1n0IOzX3$CN@xH8 literal 0 HcmV?d00001 diff --git a/link/05_Тёплый.lnk b/link/05_Тёплый.lnk new file mode 100644 index 0000000000000000000000000000000000000000..c0225a5bafa51e897cb7bb8a09b8767582c0e503 GIT binary patch literal 1473 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 literal 0 HcmV?d00001 diff --git a/link/08_Темнее.lnk b/link/08_Темнее.lnk new file mode 100644 index 0000000000000000000000000000000000000000..401c2c28658a346744eb47f4672fe8fec8c50b34 GIT binary patch literal 1485 zcmbu8T}V_>5XWcz=!1f^%tDPM$b@oncgrub(p`0}B_%cdI9XTS)mdFvhcgq3g$~oEL`M*Q#Fmrd}H6=ITWQM`_kdSDIrX8MHn>FDA51+u3Jp z8!74jS(p4V0s9hY`d$R~EEyhr{?_)=@Me0ocN2()$#zMOqEqChII`vz2j zYgSU0s@fE=V-PwnSiYbiq@DfX}5u@AtN@nD}JKAKQJ_ zKJ{!**z|dh9fG24y`>mgRHfRZaJTA`JvtWm@ZFPwGu<e)CS(uRsSx_$SZeHo8yXsm?Nosic(vG^Vi|%?nZm3}wlmbKX z!D~y94}m@8gCqqhA(e!ocD0v^$hRU?h&Y3wjBGQEg zz!HtBmNJGhMzP=B`MoLqrS9R=_SbdOuXKxZxktxQTCl3Cno|KOv?VJuBCtT&*KcVV z%j^1488;h)eK9n5Cj@&|jt;$;vAoi~om=nO2IA@ZTPit>n#e_+?Ry+{}#oce7S=$^NOe3?~Z;wrWd2_{aG_@sj>_O$|&9nO{n1XN*pYw{O z5?lJQtb!o5sSqipS7uQ1p`$W~n@RncxgX0l-Br@t}{moJfx zs;LaLD(niQ!r#0d;AJb-4n=a+@=c3*UUPZH_k=ys18_(T!iDa zk#aS1L(yO%$PJ!~YB$kdjmxWEajulP%H(R;wLVOC_k8UEw*hZn0XB|OI0=n;Qanxt zvmn=c_|O>?!s18WBr&y4rLJI{JgWih$NYBcWVx9%G>_GL+w+d*DOp_SM{lWKI!z zwRWXelsjEcMQA-H)S8RMN{NLJs|_ZDQTS$#*3jb-e%8OKgik|=KMKh!OC|81EqpqG zJwnDFrv@p&jgKj9U2RI*GIq40M~#)La|wH@u)|H26i?Cr%@ANa zL9~PDp%OJ0H}24@Sw~Y8Zgrsa{K1zI_5FmTz~|zi+xr^VOnua4O&qvjoqm2WXs(@O z$Dt@xUp___St@f$+@G4HjZZ}0fA{3@`L6k)p(8hbY&8aKGLO%%y`J!?_ffX{-oN5g z-~@x;g3*wZoilSWKV!s}WFGz&+7Z+1nsQ-XZM-dTxPx+F4bXU~QT0p|N6Ewmio>&u z64Y1%{6wrLVrHZy#A6XLVU~n>BCG*XX7MTfR+=PZ5XWatD@jPrOw>?y%w}TNhpTcHK}TDwG04 z=^?*bf_w<BlGB^pyL<&9uWX1}NFXKU_jU2)jx+oqRqbc=JjSI1FWu&S$?Qz2$bD#(vtQK9S^ zu(gkub^`oZ*=cw*Za1B80~(ql4Gcqe3VGmypp_FGN`oj z!5eB#^GHP=B)d;a*ty0P15_fj~8;vPQd z7nzlMocVm5b6lbFmd!oo1X)PNV8nk-silIYGysh9BDPET%c^dhq#XN6#{9@t=GyB%S&`LG|q8PRzD+N>+tVABd zaf+i-jofm0=n%*)zFKbs(Qb{)tA25=in+=}1Z-3Qlilp^z2LUs&1=ENQHmv@F;9x8 z7BDMH?S%)AQ};mqbiKtGi?<)u4Izce4F&nW+&M- zI!F8>x0>l8lbmyC0C~}s19EO6JvpfvN)!)00ZoK*y?NXYWs#x${@eqf)}=;p7n0R1 z${L+Yqo{Fv+=|e8OsF*%ianAN>L{Hy{Wn8^ z?FP{aqK_)oTpDnPUd?(FB_kg%=1$^I9!Avv3u1=P~r|@HGl7*2I8H)AT20LZ@fPJt2dUkWi dMy%KJ9y|1CTgS)KUv;_N+f8$kVS;m8lHbr@NX!5L literal 0 HcmV?d00001 diff --git a/link/11_Тускло.lnk b/link/11_Тускло.lnk new file mode 100644 index 0000000000000000000000000000000000000000..ca618bf4477c6321bc4fd5a478149a879aff595c GIT binary patch literal 1477 zcmbu8T}V_>5XWat`ye4rvrt13vY=et-87Xfbyr<$Nl6Vqj@ebWb(9y@h&ZC)l}8#fHaE9%Z*%9svI1# zbWRlaE!HJ|iAP*K&E5+^%=+=sS05~Ibnj=kcy=;zDD)kZ!>OG-6iw!w!kh>)sI-#u zo|?^+Da#hgBf;Z|EIr;o^TOKg*lHU4vg^*o!s17bl9<}8Qr9ucXY}4s{HP`njo9BtRhYSFI!dz~u_UYcHtX#zcCu=8 z-tmg;YNiKFan6B0@5XWcLJ|rQ{kHm~b$c%Dvcgrub(p_z>B_%WbIB8ei)bvNRoUjLItshKCJ&-uewV?56xxf%sF$;@65e3*G5FT zFh5wLVbxOhAjTN>uV4GwlJ#CU@h<#B&<>Z<0fUm9)8ITYnzq#WqA zw2c&WEmkMa#9?0?%{~gop7p~6Z>KGjx{tHr?j0Z|?Mzk4VbnryiY9Y*es%;IR9az4 zXXPf!kY$VHmayWEDk{A`_1fCu*lZe_iGMgU_HF96<3vW=p^<0R!w)VTq(BP5J$%k1 zQp)r=Gk=NORqv93bF7Wb{I)@^;8u+H=JnyGcWGAae z=ZHsSRWRLeoO2HFA}_nLSI&*3Cp$GkiK3wO&}b;vo5utwiwx!W=N|YH8Xv@6Nai$A z&|p^@M5WW^RD{-JLan)2u9R5%u-afU7=?G{Xbn9c;bVQ9N_aJd_@j`#vRVS)+0v&I zNPcz4>I=t^nbL4`0ouWi#%O?FNlvtf6Ei-=En`P3y46^@I+w7g20Lo0nvyB@zZn8- zCx~_s-BhOLQj0tEYSyt7gH=2H8sx$seGO61FGw1EE)Y7t=iJ8ed0o!v;mg*EH%9_y zuX5}Z6vgT(#K=0.19.0 +customtkinter>=5.2.0 +pyinstaller>=6.0.0 \ No newline at end of file diff --git a/silent_send.py b/silent_send.py new file mode 100644 index 0000000..cbe71b0 --- /dev/null +++ b/silent_send.py @@ -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() \ No newline at end of file