From 95896496632dd5d1746a7018487e13841792c4a6 Mon Sep 17 00:00:00 2001 From: dewgenenny Date: Sun, 3 Dec 2023 11:07:54 +0100 Subject: [PATCH] Add support for removing bulbs, saving general settings. --- .../bulb_control/magichome_bulb.py | 7 +- .../screen_sync/bulb_control/tuya_bulb.py | 2 +- screensync/screen_sync/bulb_factory.py | 41 +++-- screensync/screen_sync/config_manager.py | 10 +- screensync/screen_sync/ui/remove_bulb.py | 14 ++ screensync/ui.py | 158 ++++++++---------- setup.py | 10 +- 7 files changed, 127 insertions(+), 115 deletions(-) create mode 100644 screensync/screen_sync/ui/remove_bulb.py diff --git a/screensync/screen_sync/bulb_control/magichome_bulb.py b/screensync/screen_sync/bulb_control/magichome_bulb.py index cde3916..fb2e2cc 100644 --- a/screensync/screen_sync/bulb_control/magichome_bulb.py +++ b/screensync/screen_sync/bulb_control/magichome_bulb.py @@ -5,7 +5,7 @@ class FluxLedBulbControl: def __init__(self, ip_address, placement, rate_limiter): - self.bulb = WifiLedBulb(ip_address) + self.bulb = WifiLedBulb(ip_address, timeout=1) self.rate_limiter = rate_limiter self.last_color = None self.placement = placement @@ -28,4 +28,7 @@ def set_color(self, r, g, b): self.last_color = new_color # Store the new color def connect(self): - self.bulb.connect() + try: + self.bulb.connect(retry=1) + except: + print("Bulb " + self.type + " - " + self.ip + "Unavailable") diff --git a/screensync/screen_sync/bulb_control/tuya_bulb.py b/screensync/screen_sync/bulb_control/tuya_bulb.py index 2b37aa8..16468df 100644 --- a/screensync/screen_sync/bulb_control/tuya_bulb.py +++ b/screensync/screen_sync/bulb_control/tuya_bulb.py @@ -29,7 +29,7 @@ def __init__(self, device_id, local_key, ip, rate_limiter, placement): def connect(self): - self.bulb = tinytuya.BulbDevice(self.device_id, self.ip, self.local_key, persist=True) + self.bulb = tinytuya.BulbDevice(self.device_id, self.ip, self.local_key, persist=True, connection_timeout=1, connection_retry_limit=3, connection_retry_delay=1) self.bulb.set_version(3.3) self.bulb.set_socketRetryLimit(1) self.bulb.set_socketTimeout(1) diff --git a/screensync/screen_sync/bulb_factory.py b/screensync/screen_sync/bulb_factory.py index 0f9efb4..5f013a4 100644 --- a/screensync/screen_sync/bulb_factory.py +++ b/screensync/screen_sync/bulb_factory.py @@ -19,24 +19,33 @@ def create_bulbs(self): rate_limiter = RateLimiter(frequency) # Instantiate RateLimiter placement = bulb_config.get('placement', 'center') if bulb_config['type'] == 'MagicHome': - bulb = FluxLedBulbControl(bulb_config['ip_address'], placement, rate_limiter) - bulbs.append(bulb) + try: + bulb = FluxLedBulbControl(bulb_config['ip_address'], placement, rate_limiter) + bulbs.append(bulb) + except: + print("Error adding " + bulb.type + " bulb with IP " + bulb_config['ip_address'] ) elif bulb_type == 'Tuya': - bulb = TuyaBulbControl(bulb_config['device_id'], bulb_config['local_key'], bulb_config['ip_address'], rate_limiter, placement) - bulbs.append(bulb) + try: + bulb = TuyaBulbControl(bulb_config['device_id'], bulb_config['local_key'], bulb_config['ip_address'], rate_limiter, placement) + bulbs.append(bulb) + except: + print("Error adding " + bulb.type + " bulb with IP " + bulb_config['ip_address'] ) elif bulb_type == 'MQTT': - bulb = ZigbeeBulbControl( - mqtt_broker=mqtt_settings['broker'], - port=mqtt_settings['port'], - username=mqtt_settings['username'], - password=mqtt_settings['password'], - topic=bulb_config['topic'], - rate_limiter=rate_limiter, - placement=placement - ) - bulb.turn_on() - bulb.connect() - bulbs.append(bulb) + try: + bulb = ZigbeeBulbControl( + mqtt_broker=mqtt_settings['broker'], + port=mqtt_settings['port'], + username=mqtt_settings['username'], + password=mqtt_settings['password'], + topic=bulb_config['topic'], + rate_limiter=rate_limiter, + placement=placement + ) + bulb.turn_on() + bulb.connect() + bulbs.append(bulb) + except: + print("Error adding " + bulb.type + " bulb with MQTT broker " + mqtt_broker ) pass # Add more conditions for other bulb types diff --git a/screensync/screen_sync/config_manager.py b/screensync/screen_sync/config_manager.py index 68b073e..1427974 100644 --- a/screensync/screen_sync/config_manager.py +++ b/screensync/screen_sync/config_manager.py @@ -15,7 +15,6 @@ def create_default_config(self): """Creates a default configuration file.""" # Add default sections and settings self.config['General'] = { - 'screen_capture_size': '100, 100', 'saturation_factor': '1.5' } self.config['MQTT'] = { @@ -63,7 +62,7 @@ def get_general_settings(self): """Retrieves general settings from the config.""" general = self.config['General'] return { - 'screen_capture_size': tuple(map(int, general.get('screen_capture_size', '100, 100').split(','))), + # 'screen_capture_size': tuple(map(int, general.get('screen_capture_size', '100, 100').split(','))), 'saturation_factor': general.getfloat('saturation_factor', 1.5) } @@ -168,7 +167,7 @@ def _add_tuya_bulb(self, device_id, local_key, ip_address, placement): def _add_magichome_bulb(self, ip_address, placement): """Adds a new Tuya bulb configuration.""" tuya_bulb_count = len([s for s in self.config.sections() if s.startswith('BulbTuya')]) - section_name = f'BulbTuya{tuya_bulb_count + 1}' + section_name = f'BulbMagicHome{tuya_bulb_count + 1}' self.config[section_name] = { 'ip_address': ip_address, @@ -190,6 +189,11 @@ def set_update_frequency(self, bulb_type, frequency): self.config[section]['update_frequency'] = str(frequency) self.save_config() + def remove_bulb(self, config_section): + if config_section in self.config.sections(): + self.config.remove_section(config_section) + self.save_config() + # Example Usage if __name__ == "__main__": diff --git a/screensync/screen_sync/ui/remove_bulb.py b/screensync/screen_sync/ui/remove_bulb.py new file mode 100644 index 0000000..a2568f0 --- /dev/null +++ b/screensync/screen_sync/ui/remove_bulb.py @@ -0,0 +1,14 @@ +# remove_bulb.py + +import tkinter as tk +from tkinter import messagebox + +def create_remove_bulb_button(bulb_window, config_manager, config_section, refresh_callback): + def remove_bulb(): + if messagebox.askyesno("Remove Bulb", "Are you sure you want to remove this bulb?"): + config_manager.remove_bulb(config_section) + refresh_callback() + bulb_window.destroy() + + remove_button = tk.Button(bulb_window, text="Remove", command=remove_bulb, bg='red', fg='white') + return remove_button diff --git a/screensync/ui.py b/screensync/ui.py index fe23653..d2c2fb2 100644 --- a/screensync/ui.py +++ b/screensync/ui.py @@ -1,12 +1,12 @@ import tkinter as tk -from tkinter import PhotoImage -from tkinter import Toplevel, Label, Entry, Button, Listbox,LabelFrame, END -from tkinter import ttk +from tkinter import PhotoImage, Toplevel, Label, Entry, Button, Listbox,LabelFrame, ttk, messagebox, END from PIL import Image, ImageTk import PIL from platformdirs import * import os from screensync.screen_sync.ui.add_bulb import create_add_bulb_window +from screensync.screen_sync.ui.remove_bulb import create_remove_bulb_button + import pkg_resources appname = 'ScreenSync_v2' @@ -31,7 +31,7 @@ def main(): # Check if config directory exists and if not create os.makedirs(user_data_dir(appname, appauthor), exist_ok=True) - print(user_data_dir(appname, appauthor) + '/config.ini') + #print(user_data_dir(appname, appauthor) + '/config.ini') # Initialize necessary objects config_manager = ConfigManager(user_data_dir(appname, appauthor) + '/config.ini') @@ -125,11 +125,11 @@ def on_closing(root, coordinator): def reinitialize_bulbs(): global config_manager config_manager = ConfigManager('./config.ini') - global bulbs # If bulbs are defined globally + global bulbs bulbs = bulb_factory.create_bulbs() # Recreate bulbs with new settings global coordinator coordinator = Coordinator(bulbs, color_processing) - print(bulbs) + def shooter_clicked(shooter_button, coordinator): @@ -164,10 +164,10 @@ def open_general_settings(config_manager): saturation_var = tk.StringVar(value=general_settings.get('saturation_factor', '1.5')) Entry(general_settings_window, textvariable=saturation_var).grid(row=0, column=1) - # Screen Capture Size Setting - Label(general_settings_window, text="Screen Capture Size:").grid(row=1, column=0, sticky='e') - capture_size_var = tk.StringVar(value=general_settings.get('screen_capture_size', '100, 100')) - Entry(general_settings_window, textvariable=capture_size_var).grid(row=1, column=1) +# # Screen Capture Size Setting +# Label(general_settings_window, text="Screen Capture Size:").grid(row=1, column=0, sticky='e') +# capture_size_var = tk.StringVar(value=general_settings.get('screen_capture_size', '100, 100')) +# Entry(general_settings_window, textvariable=capture_size_var).grid(row=1, column=1) # Save Button save_button = Button(general_settings_window, text="Save", @@ -175,44 +175,12 @@ def open_general_settings(config_manager): save_button.grid(row=2, column=0, columnspan=2) -def open_settings_window(root, coordinator, config_manager , bulb_factory): - - def refresh_bulb_list(): - bulbs_listbox.delete(0, tk.END) # Clear the existing list - bulbs = config_manager.get_bulbs() # Retrieve updated list of bulbs - for bulb in bulbs: - bulbs_listbox.insert(tk.END, f"{bulb['config_id']} - {bulb['device_id']} - {bulb['placement']}") - reinitialize_bulbs() +def create_settings_frame(parent, title, settings, entries_dict): + frame = tk.LabelFrame(parent, text=title, bg='#404957', fg='white', font=("TkDefaultFont", 12, "bold")) + frame.pack(padx=10, pady=10, fill='x') - settings_window = tk.Toplevel(root) - settings_window.title("Settings") - settings_window.geometry("400x700") # Adjust the size as needed - settings_window.configure(bg='#404957') - settings_window.resizable(False, False) - - # General settings frame - general_settings_frame = tk.LabelFrame(settings_window, text="General", bg='#404957', fg='white', font=("TkDefaultFont", 12, "bold")) - general_settings_frame.pack(padx=10, pady=10, fill='x') - # MQTT settings frame - mqtt_settings_frame = tk.LabelFrame(settings_window, text="MQTT Server", bg='#404957', fg='white', font=("TkDefaultFont", 12, "bold")) - mqtt_settings_frame.pack(padx=10, pady=10, fill='x') - # Tuya settings frame - tuya_settings_frame = tk.LabelFrame(settings_window, text="Tuya Specific", bg='#404957', fg='white', font=("TkDefaultFont", 12, "bold")) - tuya_settings_frame.pack(padx=10, pady=10, fill='x') - # MQTT settings frame - mqtt_specific_settings_frame = tk.LabelFrame(settings_window, text="MQTT Specific", bg='#404957', fg='white', font=("TkDefaultFont", 12, "bold")) - mqtt_specific_settings_frame.pack(padx=10, pady=10, fill='x') - # MagicHome settings frame - magichome_specific_settings_frame = tk.LabelFrame(settings_window, text="MagicHome Specific", bg='#404957', fg='white', font=("TkDefaultFont", 12, "bold")) - magichome_specific_settings_frame.pack(padx=10, pady=10, fill='x') - - add_new_frame = tk.LabelFrame(settings_window, text="Add New Bulb", bg='#404957', fg='white', font=("TkDefaultFont", 12, "bold")) - add_new_frame.pack(padx=10, pady=10, fill='x') - - # Retrieve general settings and create a label and entry for each setting - general_settings = config_manager.get_general_settings() - for setting, value in general_settings.items(): - row = tk.Frame(general_settings_frame, bg='#404957') + for setting, value in settings.items(): + row = tk.Frame(frame, bg='#404957') row.pack(side='top', fill='x', padx=5, pady=5) label = tk.Label(row, text=setting.replace('_', ' ').title() + ":", bg='#404957', fg='white') @@ -221,58 +189,68 @@ def refresh_bulb_list(): entry = tk.Entry(row, bg='white', fg='black') entry.pack(side='right', expand=True, fill='x') entry.insert(0, value) + entries_dict[setting] = entry - mqtt_settings = config_manager.get_mqtt_settings() - - for setting, value in mqtt_settings.items(): - row = tk.Frame(mqtt_settings_frame, bg='#404957') - row.pack(side='top', fill='x', padx=5, pady=5) - - label = tk.Label(row, text=setting.replace('_', ' ').title() + ":", bg='#404957', fg='white') - label.pack(side='left') + return frame - entry = tk.Entry(row, bg='white', fg='black') - entry.pack(side='right', expand=True, fill='x') - entry.insert(0, value) +def open_settings_window(root, coordinator, config_manager , bulb_factory): - tuya_settings = config_manager.get_config_by_section("TuyaSettings") - for setting, value in tuya_settings.items(): - row = tk.Frame(tuya_settings_frame, bg='#404957') - row.pack(side='top', fill='x', padx=5, pady=5) + # This dictionary will hold the entry widgets for settings + settings_entries = { + 'General': {}, + 'MQTT': {}, + 'TuyaSettings': {}, + 'MQTTSettings': {}, + 'MagicHomeSettings': {} + } - label = tk.Label(row, text=setting.replace('_', ' ').title() + ":", bg='#404957', fg='white') - label.pack(side='left') + def save_settings(): + # Iterate over each settings section and update the configuration + for section, entries in settings_entries.items(): + for setting, entry in entries.items(): + config_manager.config[section][setting] = entry.get() - entry = tk.Entry(row, bg='white', fg='black') - entry.pack(side='right', expand=True, fill='x') - entry.insert(0, value) + # Save the updated configuration to the file + config_manager.save_config() - mqtt_specific_settings = config_manager.get_config_by_section("MQTTSettings") + # Refresh the bulbs and UI if necessary + refresh_bulb_list() - for setting, value in mqtt_specific_settings.items(): - row = tk.Frame(mqtt_specific_settings_frame, bg='#404957') - row.pack(side='top', fill='x', padx=5, pady=5) + # Provide feedback that settings have been saved + messagebox.showinfo("Settings", "Settings have been saved successfully.") - label = tk.Label(row, text=setting.replace('_', ' ').title() + ":", bg='#404957', fg='white') - label.pack(side='left') - entry = tk.Entry(row, bg='white', fg='black') - entry.pack(side='right', expand=True, fill='x') - entry.insert(0, value) + def refresh_bulb_list(): + bulbs_listbox.delete(0, tk.END) # Clear the existing list + bulbs = config_manager.get_bulbs() # Retrieve updated list of bulbs + for bulb in bulbs: + bulbs_listbox.insert(tk.END, f"{bulb['config_id']} - {bulb['device_id']} - {bulb['placement']}") + reinitialize_bulbs() - magichome_specific_settings = config_manager.get_config_by_section("MagicHomeSettings") + settings_window = tk.Toplevel(root) + settings_window.title("Settings") + settings_window.geometry("400x700") # Adjust the size as needed + settings_window.configure(bg='#404957') + settings_window.resizable(False, False) - for setting, value in magichome_specific_settings.items(): - row = tk.Frame(magichome_specific_settings_frame, bg='#404957') - row.pack(side='top', fill='x', padx=5, pady=5) + # General settings frame + general_settings_frame = create_settings_frame(settings_window, "General", config_manager.get_general_settings(), settings_entries['General']) + # MQTT settings frame + mqtt_settings_frame = create_settings_frame(settings_window, "MQTT Server", config_manager.get_mqtt_settings(), settings_entries['MQTT']) + # Tuya settings frame + tuya_settings_frame = create_settings_frame(settings_window, "Tuya Specific", config_manager.get_config_by_section("TuyaSettings"), settings_entries['TuyaSettings']) + # MQTT specific settings frame + mqtt_specific_settings_frame = create_settings_frame(settings_window, "MQTT Specific", config_manager.get_config_by_section("MQTTSettings"), settings_entries['MQTTSettings']) + # MagicHome settings frame + magichome_specific_settings_frame = create_settings_frame(settings_window, "MagicHome Specific", config_manager.get_config_by_section("MagicHomeSettings"), settings_entries['MagicHomeSettings']) - label = tk.Label(row, text=setting.replace('_', ' ').title() + ":", bg='#404957', fg='white') - label.pack(side='left') + # Add "Save Settings" Button + save_button = tk.Button(settings_window, text="Save Settings", command=save_settings, bg='green', fg='white') + save_button.pack(side='bottom', pady=10) - entry = tk.Entry(row, bg='white', fg='black') - entry.pack(side='right', expand=True, fill='x') - entry.insert(0, value) + add_new_frame = tk.LabelFrame(settings_window, text="Add New Bulb", bg='#404957', fg='white', font=("TkDefaultFont", 12, "bold")) + add_new_frame.pack(padx=10, pady=10, fill='x') # Bulbs listbox with a scrollbar bulbs_frame = tk.LabelFrame(settings_window, text="Bulbs", bg='#404957', fg='white', font=("TkDefaultFont", 12, "bold")) @@ -301,7 +279,7 @@ def refresh_bulb_list(): def on_bulb_select(event): selected_bulb = bulbs_listbox.get(bulbs_listbox.curselection()) - open_bulb_settings(root, coordinator,config_manager, bulb_factory, selected_bulb.split(' - ')[0]) # Assuming device_id is after '-' + open_bulb_settings(root, coordinator,config_manager, bulb_factory,refresh_bulb_list, selected_bulb.split(' - ')[0]) # Assuming device_id is after '-' bulbs_listbox.bind('<>', on_bulb_select) @@ -323,7 +301,9 @@ def center_window_on_screen(window): -def open_bulb_settings(root, coordinator, config_manager, bulb_factory, config_section): +def open_bulb_settings(root, coordinator, config_manager, bulb_factory,refresh_bulb_list, config_section): + + bulb_window = Toplevel(root) bulb_window.title(f"Settings for Bulb: {config_section}") bulb_window.configure(bg='#404957') @@ -357,7 +337,9 @@ def save_bulb_settings(): # Save Button save_button = Button(bulb_window, text="Save", command=save_bulb_settings) save_button.pack(pady=10) - + # Create and place the Remove Button + remove_button = create_remove_bulb_button(bulb_window, config_manager, config_section, refresh_bulb_list) + remove_button.pack(pady=(0, 10)) # Adjust padding as needed # Place focus on the window (optional) bulb_window.focus_force() diff --git a/setup.py b/setup.py index 22fd0b5..e2862d5 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,11 @@ from setuptools import setup, find_packages setup( - name='ScreenSync', # Replace with your package name - version='0.0.1', # Initial version - author='Tom George', # Replace with your name - author_email='tom@penberth.com', # Replace with your email - description='A Python tool for synchronizing screen colors with smart bulbs.', # Short description + name='ScreenSync', + version='0.0.2', + author='Tom George', + author_email='tom@penberth.com', + description='A Python tool for synchronizing screen colors with smart bulbs.', long_description=open('README.md').read(), long_description_content_type='text/markdown', url='https://github.com/dewgenenny/ScreenSync',