def __init__(self, logger=None): self.logger = logger or logging.getLogger(__name__) self.logger.info("Starting Lextend Engine.") # Load configuration from XML file try: local_ip = get_local_ip() except: local_ip = "" self.cfg = ConfigManager(CONFIGURATION_SUBDIRECTORY, CONFIGURATION_FILENAME, lextend_ip = local_ip) # Create a sonos manager self.sonosPoolManager = SonosPoolManager() try: self.sonosPoolManager.discover() except: self.logger.error("Could not start discovering Sonos.") # Create a sounds manager self.soundsManager = SoundsManager(CONFIGURATION_SUBDIRECTORY) # Expose some RPC interfaces for webfrontend self.rpc_server_thread = RPC_Server(self.sonosPoolManager) self.rpc_server_thread.start() # Get aria ip address while True: Lextend_ip_address = get_local_ip() if Lextend_ip_address == "": self.logger.error("FATAL: Cannot get my IP address. Retrying in 10s.") sleep(10) else: self.logger.info("Detected IP address : %s" % Lextend_ip_address) break self.CIFS_HEADER = "x-file-cifs://" + Lextend_ip_address + "/sonos_share/" # Start listening to miniserver. self.sock = None while self.sock == None: try: self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock.bind(("", UDP_PORT)) except Exception: # NOTE : the common error is : 98 : address already in use. self.logger.error("Failed to create socket, retrying in 10 seconds.", exc_info=True) sleep(10)
class LextendEngine(object): def __init__(self, logger=None): self.logger = logger or logging.getLogger(__name__) self.logger.info("Starting Lextend Engine.") self.doorbell_thread = None self.alarm_thread = None self.socket_receiver_thread = None self.commercial_thread = None self.commercial_thread_event = threading.Event() self.announcementStarted = False self.alarmStarted = False # Load configuration from XML file try: local_ip = get_local_ip() except: local_ip = "" self.cfg = ConfigManager(CONFIGURATION_SUBDIRECTORY, CONFIGURATION_FILENAME, lextend_ip=local_ip) try: self.setup_gpios() except: self.logger.error("Failed to setup gpios.", exc_info=True) # Create a sonos manager self.sonosPoolManager = SonosPoolManager() try: self.sonosPoolManager.discover() except: self.logger.error("Could not start discovering Sonos.") # Create a sounds manager self.soundsManager = SoundsManager(CONFIGURATION_SUBDIRECTORY) # Expose some RPC interfaces for webfrontend self.rpc_server_thread = RPC_Server(self.sonosPoolManager) self.rpc_server_thread.start() # Get aria ip address while True: Lextend_ip_address = get_local_ip() if Lextend_ip_address == "": self.logger.error("FATAL: Cannot get my IP address. Retrying in 10s.") sleep(10) else: self.logger.info("Detected IP address : %s" % Lextend_ip_address) break self.CIFS_HEADER = "x-file-cifs://" + Lextend_ip_address + "/sonos_share/" self.CIFS_HEADER_CUSTOM = "x-file-cifs://" + Lextend_ip_address + "/sonos_share/smb/" self.CIFS_HEADER_COMMERCIALS = "x-file-cifs://" + Lextend_ip_address + "/" # Start listening to miniserver. self.sock = None while self.sock is None: try: self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock.bind(("", UDP_PORT)) except Exception: # NOTE : the common error is : 98 : address already in use. self.logger.error("Failed to create socket, retrying in 10 seconds.", exc_info=True) sleep(10) def set_network(self): """ Set /etc/network/interfaces settings from loaded settings. """ if self.cfg.general.lextend.wifi.enable == True: if self.cfg.general.wifi_static.enable == True: # static change_wifi_static(self.cfg.general.wifi_static.ip, self.cfg.general.wifi_static.netmask, self.cfg.general.wifi_static.gateway, self.cfg.general.wifi_static.dns1, self.cfg.general.wifi_static.dns2) elif self.cfg.general.wifi_static.enable == False: # dhcp change_wifi_dhcp() change_wpa_supplicant(self.cfg.general.wifi_dhcp.ssid, self.cfg.general.wifi_dhcp.password) elif self.cfg.general.lextend.wifi.enable == False: if self.cfg.general.lextend.enable == True: change_ethernet_static(self.cfg.general.lextend.ip, self.cfg.general.lextend.netmask, self.cfg.general.lextend.gateway, self.cfg.general.lextend.dns1, self.cfg.general.lextend.dns2) elif self.cfg.general.lextend.enable == False: change_ethernet_dhcp() def reset_do(self): self.logger.info("GPIO: config reset.") self.cfg.lextend_ip = "192.168.1.222" # Force reset of ip address too. self.cfg.reset() # blink status led for x in range(10): GPIO.output(STATUS_LED_PIN, 0) sleep(0.2) GPIO.output(STATUS_LED_PIN, 1) sleep(0.2) logger.info("setting network config") self.set_network() logger.info("rebooting in 2 seconds ...") time.sleep(2) logger.info("rebooting now") os.system("reboot") def reset_but_callback(self, channel): global start global end self.logger.info("GPIO: reset button triggered.") if GPIO.input(CONFIG_RESET_BUTTON_PIN) == 0: self.logger.info("GPIO: reset button low.") start = time.time() if GPIO.input(CONFIG_RESET_BUTTON_PIN) == 1: self.logger.info("GPIO: reset button high.") end = time.time() elapsed = end - start if elapsed > CONFIG_RESET_HOLD_TIME_SEC: self.reset_do() else: self.logger.info("GPIO: config reset pressed, bu was not long enough.") def pin_cb_both(self, channel): # gpios = [33, 35, 36, 37, 38, 40] # new mapping after test with pcb corresponds to P1..P6 gpios = [33, 40, 38, 36, 37, 35] try: i = gpios.index(channel) if GPIO.input(channel) == 0: # inverse logic on the pcb rising = True self.logger.info("GPIO: triggered on rising edge. channel %s" % channel) packet = self.cfg.gpio.udp_payload_rising[i] else: rising = False self.logger.info("GPIO: triggered on falling edge. channel %s" % channel) packet = self.cfg.gpio.udp_payload_falling[i] sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) if len(packet) > 0: self.logger.info("GPIO: sending %s to lo" % packet) sock.sendto(packet, ("0.0.0.0", 5050)) except: self.logger.error("GPIO no supported.", exc_info=True) def setup_gpios(self): self.logger.info("Setting up GPIOs") GPIO.cleanup() GPIO.setmode(GPIO.BOARD) # Status LED GPIO.setup(STATUS_LED_PIN, GPIO.OUT) GPIO.output(STATUS_LED_PIN, 0) # Long press (5 seconds) on RESET BUTTON (31), resets config. GPIO.setup(CONFIG_RESET_BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.add_event_detect(CONFIG_RESET_BUTTON_PIN, GPIO.BOTH, callback=self.reset_but_callback, bouncetime=200) # Input gpios gpios = [33, 35, 36, 37, 38, 40] for i in range(6): self.logger.info("GPIO PIN%s => falling: %s, rising: %s" % (gpios[i], self.cfg.gpio.udp_payload_falling[i], self.cfg.gpio.udp_payload_rising[i])) GPIO.setup(gpios[i], GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.add_event_detect(gpios[i], GPIO.BOTH, callback=self.pin_cb_both, bouncetime=200) def commercials_loop(self, freq=None, vol=None, cgroups=None): if freq: frequency = freq else: frequency = 10 # by default hardcoded value of frequency. while not self.commercial_thread_event.isSet(): commercial_filename = self.soundsManager.get_random_commercial_path() if commercial_filename: split = os.path.split(commercial_filename) sound_file = os.path.join(os.path.split(split[0])[1], split[1]) smb_path = self.CIFS_HEADER_COMMERCIALS + sound_file self.logger.info("commercial : Playing %s, %s, %s" % (sound_file, smb_path, cgroups)) if self.doorbell_thread: if self.doorbell_thread.isAlive(): self.logger.info("Dropping commercial because it's already playing") continue if cgroups is None: cgroups = [] if cgroups: sfx = "-Z" else: sfx = None if vol: volume = vol else: volume = self.cfg.sonos_doorbell.commercials.volume # override if self.cfg.sonos_doorbell.commercials.volume_override: volume = self.cfg.sonos_doorbell.commercials.volume self.doorbell_thread = self.sonosPoolManager.pause_play_bell_resume(smb_path, volume, groups=cgroups, splitter=sfx, ignore_sonos_names_enabled=False) self.logger.info("starting thread %s " % self.doorbell_thread) self.doorbell_thread.start() self.doorbell_thread.join() else: self.logger.error("No Commercials found") if not self.commercial_thread_event.isSet(): self.logger.info("Commercial. Waiting for %s min." % frequency) self.commercial_thread_event.wait(60*frequency) self.logger.info("Commercial. Thread end.") def run(self): self.socket_receiver_thread = Thread(target=self.socket_receiver, args=()) self.socket_receiver_thread.setDaemon(True) self.socket_receiver_thread.start() GPIO.output(STATUS_LED_PIN, 1) # Threads shouldn't stop self.socket_receiver_thread.join() def socket_receiver(self): self.logger.info("Started listening on UDP port %s" % UDP_PORT) # t = None while True: data, addr = self.sock.recvfrom(UDP_PORT) # drop the packet if there is a bell in progress. # if t: # if t.isAlive(): # self.logger.info("Dropping packet: bell in progress.") # continue try: ret = parseExtensionProtocol(data, str(self.cfg)) if ret: if ret["type"] in "GeneralFeatures_Alarm": if ret["command"] == 1: # start sound = ret["params"][0] volume = ret["params"][1] # Calculate volume percentage from protocol input. # volume = volume * 11 # [0,9] => [0-100] # Search for the sound in uploads and fallback to defaults. if self.cfg.sonos_doorbell.default_sound != 0: sound = self.cfg.sonos_doorbell.default_sound default_sound = True if "default sound" != self.cfg.sonos_doorbell.sounds_filelist[sound-1]: default_sound = False sound_file = self.soundsManager.search_path_by_index(sound, default_sound) if default_sound: if sound_file: split = os.path.split(sound_file) sound_file = os.path.join(os.path.split(split[0])[1], split[1]) smb_path = self.CIFS_HEADER + sound_file self.logger.info("Alarm : Playing %s, %s, %s." % (sound_file, smb_path, volume)) self.logger.info("Alarm : Start") # stop doorbell thread if it is running, # other commands do not use threading and long processes if self.doorbell_thread: if self.doorbell_thread.isAlive(): # self.doorbell_thread.stop() self.doorbell_thread.join() if self.alarmStarted == False: # start alarm self.alarm_thread = self.sonosPoolManager.alarm_start(smb_path, volume) self.alarm_thread.start() self.alarmStarted = True else: self.logger.error("Couldn't locate a sound @ index : %s" % sound) else: if sound_file: split = os.path.split(sound_file) sound_file = os.path.join(os.path.split(split[0])[1], split[1]) smb_path = self.CIFS_HEADER_CUSTOM + sound_file self.logger.info("Alarm : Playing %s, %s, %s." % (sound_file, smb_path, volume)) self.logger.info("Alarm : Start") # stop doorbell thread if it is running, # other commands do not use threading and long processes if self.doorbell_thread: if self.doorbell_thread.isAlive(): # self.doorbell_thread.stop() self.doorbell_thread.join() if self.alarmStarted == False: # start alarm self.alarm_thread = self.sonosPoolManager.alarm_start(smb_path, volume) self.alarm_thread.start() self.alarmStarted = True else: self.logger.error("Couldn't locate a sound @ index : %s" % sound) else: # stop self.logger.info("Alarm : Stop") if self.alarm_thread: if self.alarm_thread.isAlive(): self.alarm_thread.stop() self.alarm_thread.join() self.sonosPoolManager.alarm_stop() self.alarmStarted = False else: if self.alarm_thread: if self.alarm_thread.isAlive(): self.logger.info("Dropping other commands. Alarm is in progress.") continue if ret["type"] in "GeneralFeatures_LineIn": if ret["command"] == 1: volume = ret["params"][0] self.sonosPoolManager.annoucementStart(volume) self.announcementStarted = True else: if self.announcementStarted: self.sonosPoolManager.annoucementStop() self.announcementStarted = False elif ret["type"] in "GeneralFeatures_DoorBell": if self.cfg.sonos_doorbell.enable: sound = ret["params"][0] volume = ret["params"][1] # Calculate volume percentage from protocol input. # volume = volume * 11 # [0,9] => [0-100] # Apply configured volume override. if self.cfg.sonos_doorbell.volume_override: volume = self.cfg.sonos_doorbell.volume # Search for the sound in uploads and fallback to defaults. if self.cfg.sonos_doorbell.default_sound != 0: sound = self.cfg.sonos_doorbell.default_sound default_sound = True if "default sound" != self.cfg.sonos_doorbell.sounds_filelist[sound-1]: default_sound = False sound_file = self.soundsManager.search_path_by_index(sound, default_sound) if default_sound: if sound_file: split = os.path.split(sound_file) sound_file = os.path.join(os.path.split(split[0])[1], split[1]) smb_path = self.CIFS_HEADER + sound_file self.logger.info("Playing %s, %s, %s." % (sound_file, smb_path, volume)) # create sonos list from active groups. # self.logger.info("Groups: %s, %s, %s." % (group1, group2, group3)) if self.doorbell_thread: if self.doorbell_thread.isAlive(): self.logger.info("Dropping doorbell because it's already playing") continue self.doorbell_thread = self.sonosPoolManager.pause_play_bell_resume(smb_path, volume, suffix="-DB", ignore_sonos_names_enabled=self.cfg.sonos_doorbell.ignore_sonos_names) self.doorbell_thread.start() else: self.logger.error("Couldn't locate a sound @ index : %s" % sound) else: if sound_file: split = os.path.split(sound_file) sound_file = os.path.join(os.path.split(split[0])[1], split[1]) smb_path = self.CIFS_HEADER_CUSTOM + sound_file self.logger.info("Playing %s, %s, %s." % (sound_file, smb_path, volume)) # create sonos list from active groups. # self.logger.info("Groups: %s, %s, %s." % (group1, group2, group3)) if self.doorbell_thread: if self.doorbell_thread.isAlive(): self.logger.info("Dropping doorbell because it's already playing") continue self.doorbell_thread = self.sonosPoolManager.pause_play_bell_resume(smb_path, volume, suffix="-DB", ignore_sonos_names_enabled=self.cfg.sonos_doorbell.ignore_sonos_names) self.doorbell_thread.start() else: self.logger.error("Couldn't locate a sound @ index : %s" % sound) else: self.logger.info("SonosDoorbell feature disabled !") elif ret["type"] in "AllSpeakersFeatures_Play": command = ret["command"] volume = None if command is 2: volume = ret["volume"][0] saved_radio = self.cfg.sonos_doorbell.saved_radios[0] if self.announcementStarted: continue else: self.sonosPoolManager.play_track_or_radio(ruri=saved_radio.url, rmeta=saved_radio.meta, command=command, volume=volume) elif ret["type"] in "AllSpeakersFeatures_Pause": if self.announcementStarted: continue else: self.sonosPoolManager.pause() elif ret["type"] in "AllSpeakersFeatures_VolUp": command = ret["command"] if command == 1: percentage = 1 elif command == 2: percentage = 5 if self.announcementStarted: continue else: self.sonosPoolManager.volup(percentage=percentage) elif ret["type"] in "AllSpeakersFeatures_VolDown": command = ret["command"] if command == 1: percentage = 1 elif command == 2: percentage = 5 if self.announcementStarted: continue else: self.sonosPoolManager.voldown(percentage=percentage) elif ret["type"] in "AllSpeakersFeatures_TrackUp": if self.announcementStarted: continue else: self.sonosPoolManager.previous() elif ret["type"] in "AllSpeakersFeatures_TrackDown": if self.announcementStarted: continue else: self.sonosPoolManager.next() elif ret["type"] in "AllSpeakersFeatures_NextRadioStation": if self.announcementStarted: continue else: self.sonosPoolManager.nextRadio() elif ret["type"] in "AllSpeakersFeatures_PreviousRadioStation": if self.announcementStarted: continue else: self.sonosPoolManager.previousRadio() elif ret["type"] in "AllSpeakersFeatures_SetVolume": try: volume = int(ret["volume"][0]) if self.announcementStarted: continue else: self.sonosPoolManager.volset(volume) except: self.logger.error("SetVolume protocol error %s" % ret["params"]) elif ret["type"] in "AllSpeakersFeatures_SetRadioStation": try: station = int(ret["station"][0]) saved_radio = self.cfg.sonos_doorbell.saved_radios[station] if saved_radio.url: if self.announcementStarted: continue else: self.sonosPoolManager.play_uri(saved_radio.url, saved_radio.meta) except: self.logger.error("SetRadio protocol error %s" % ret["station"]) elif ret["type"] in "AllSpeakersFeatures_Commercials_Enable": try: self.logger.info("Commercials Enable protocol.") if self.commercial_thread: if self.commercial_thread.isAlive(): self.logger.info("Commercial is already enabled") continue self.commercial_thread_event.clear() if self.announcementStarted: continue else: self.commercial_thread = threading.Thread(target=self.commercials_loop, args=(ret["params"][0], None, [])) self.commercial_thread.start() except: self.logger.error("Commercials Enable error.") elif ret["type"] in "AllSpeakersFeatures_Commercials_Disable": try: self.logger.info("Commercials Disable protocol.") if self.announcementStarted: continue else: self.commercial_thread_event.set() except: self.logger.error("Commercials Disable error.") elif ret["type"] in "ZonesFeatures_Commercials_Enable": try: self.logger.info("Commercials Enable protocol.") if self.commercial_thread: if self.commercial_thread.isAlive(): self.logger.info("Commercial is already enabled") continue self.commercial_thread_event.clear() if self.announcementStarted: continue else: self.commercial_thread = threading.Thread(target=self.commercials_loop, args=(ret["params"][1], None, ret["params"][0])) self.commercial_thread.start() except: self.logger.error("Commercials Enable error.") elif ret["type"] in "ZonesFeatures_Commercials_Disable": try: self.logger.info("Commercials Disable protocol.") if self.announcementStarted: continue else: self.commercial_thread_event.set() except: self.logger.error("Commercials Disable error.") elif ret["type"] in "ZonesFeatures_Play": command = ret["command"] saved_radio = self.cfg.sonos_doorbell.saved_radios[0] if self.announcementStarted: continue else: if command is 1: self.sonosPoolManager.play_track_or_radio(groups=ret["params"], splitter="-Z", ruri=saved_radio.url, rmeta=saved_radio.meta, command=command) elif command is 2: for i in range(1, 4, 2): volume = ret["params"][i] zone = [ret["params"][i-1]] self.sonosPoolManager.play_track_or_radio(groups=zone, splitter="-Z", ruri=saved_radio.url, rmeta=saved_radio.meta, command=command, volume=volume) elif ret["type"] in "ZonesFeatures_Pause": if self.announcementStarted: continue else: self.sonosPoolManager.pause(ret["params"], "-Z") elif ret["type"] in "ZonesFeatures_VolUp": command = ret["command"] if command == 1: percentage = 1 elif command == 2: percentage = 5 if self.announcementStarted: continue else: self.sonosPoolManager.volup(ret["params"], "-Z", percentage=percentage) elif ret["type"] in "ZonesFeatures_VolDown": command = ret["command"] if command == 1: percentage = 1 elif command == 2: percentage = 5 if self.announcementStarted: continue else: self.sonosPoolManager.voldown(ret["params"], "-Z", percentage=percentage) elif ret["type"] in "ZonesFeatures_TrackUp": if self.announcementStarted: continue else: self.sonosPoolManager.previous(ret["params"], "-Z") elif ret["type"] in "ZonesFeatures_TrackDown": if self.announcementStarted: continue else: self.sonosPoolManager.next(ret["params"], "-Z") elif ret["type"] in "ZonesFeatures_NextRadioStation": if self.announcementStarted: continue else: self.sonosPoolManager.nextRadio(ret["params"], "-Z") elif ret["type"] in "ZonesFeatures_PreviousRadioStation": if self.announcementStarted: continue else: self.sonosPoolManager.previousRadio(ret["params"], "-Z") elif ret["type"] in "ZonesFeatures_SetVolume": if self.announcementStarted: continue else: self.sonosPoolManager.volset(ret["params"][1], [ret["params"][0]], "-Z") elif ret["type"] in "ZonesFeatures_SetRadioStation": try: saved_radio = self.cfg.sonos_doorbell.saved_radios[ret["params"][1]] if saved_radio.url: if self.announcementStarted: continue else: self.sonosPoolManager.play_uri(saved_radio.url, saved_radio.meta, [ret["params"][0]], "-Z") except: self.logger.error("SetRadio protocol error %s" % ret["station"]) else: self.logger.error("Packet type not known : %s" % ret["type"]) else: self.logger.warn("Can't decode this packet : %s" % data) except: self.logger.error("In main loop : ", exc_info=True)
def __init__(self, logger=None): self.logger = logger or logging.getLogger(__name__) self.logger.info("Starting Lextend Engine.") self.doorbell_thread = None self.alarm_thread = None self.socket_receiver_thread = None self.commercial_thread = None self.commercial_thread_event = threading.Event() self.announcementStarted = False # Load configuration from XML file try: local_ip = get_local_ip() except: local_ip = "" self.cfg = ConfigManager(CONFIGURATION_SUBDIRECTORY, CONFIGURATION_FILENAME, lextend_ip=local_ip) # try: # self.setup_gpios() # except: # self.logger.error("Failed to setup gpios.", exc_info=True) # Create a sonos manager self.sonosPoolManager = SonosPoolManager() try: self.sonosPoolManager.discover() except: self.logger.error("Could not start discovering Sonos.") # Create a sounds manager self.soundsManager = SoundsManager(CONFIGURATION_SUBDIRECTORY) # self.flic = None # try: # self.flic = FlicManager(local_ip) # except: # self.logger.warning("Check whether Flic Server is running") # try: # self.flic.discover() # except: # self.logger.error("Could not start discovering Flic.") # Expose some RPC interfaces for webfrontend self.rpc_server_thread = RPC_Server(self.sonosPoolManager) self.rpc_server_thread.start() # Get aria ip address while True: Lextend_ip_address = get_local_ip() if Lextend_ip_address == "": self.logger.error( "FATAL: Cannot get my IP address. Retrying in 10s.") sleep(10) else: self.logger.info("Detected IP address : %s" % Lextend_ip_address) break self.CIFS_HEADER = "x-file-cifs://" + Lextend_ip_address + "/sonos_share/" self.CIFS_HEADER_CUSTOM = "x-file-cifs://" + Lextend_ip_address + "/sonos_share/smb/" self.CIFS_HEADER_COMMERCIALS = "x-file-cifs://" + Lextend_ip_address + "/" self.sock = None while self.sock is None: try: self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock.bind(("", UDP_PORT)) except Exception: # NOTE : the common error is : 98 : address already in use. self.logger.error( "Failed to create socket, retrying in 10 seconds.", exc_info=True) sleep(10)
class LextendEngine(object): def __init__(self, logger=None): self.logger = logger or logging.getLogger(__name__) self.logger.info("Starting Lextend Engine.") self.doorbell_thread = None self.alarm_thread = None self.socket_receiver_thread = None self.commercial_thread = None self.commercial_thread_event = threading.Event() self.announcementStarted = False # Load configuration from XML file try: local_ip = get_local_ip() except: local_ip = "" self.cfg = ConfigManager(CONFIGURATION_SUBDIRECTORY, CONFIGURATION_FILENAME, lextend_ip=local_ip) # try: # self.setup_gpios() # except: # self.logger.error("Failed to setup gpios.", exc_info=True) # Create a sonos manager self.sonosPoolManager = SonosPoolManager() try: self.sonosPoolManager.discover() except: self.logger.error("Could not start discovering Sonos.") # Create a sounds manager self.soundsManager = SoundsManager(CONFIGURATION_SUBDIRECTORY) # self.flic = None # try: # self.flic = FlicManager(local_ip) # except: # self.logger.warning("Check whether Flic Server is running") # try: # self.flic.discover() # except: # self.logger.error("Could not start discovering Flic.") # Expose some RPC interfaces for webfrontend self.rpc_server_thread = RPC_Server(self.sonosPoolManager) self.rpc_server_thread.start() # Get aria ip address while True: Lextend_ip_address = get_local_ip() if Lextend_ip_address == "": self.logger.error( "FATAL: Cannot get my IP address. Retrying in 10s.") sleep(10) else: self.logger.info("Detected IP address : %s" % Lextend_ip_address) break self.CIFS_HEADER = "x-file-cifs://" + Lextend_ip_address + "/sonos_share/" self.CIFS_HEADER_CUSTOM = "x-file-cifs://" + Lextend_ip_address + "/sonos_share/smb/" self.CIFS_HEADER_COMMERCIALS = "x-file-cifs://" + Lextend_ip_address + "/" self.sock = None while self.sock is None: try: self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock.bind(("", UDP_PORT)) except Exception: # NOTE : the common error is : 98 : address already in use. self.logger.error( "Failed to create socket, retrying in 10 seconds.", exc_info=True) sleep(10) def commercials_loop(self, freq=None, vol=None, cgroups=None): if freq: frequency = freq else: frequency = 10 # by default hardcoded value of frequency. while not self.commercial_thread_event.isSet(): commercial_filename = self.soundsManager.get_random_commercial_path( ) if commercial_filename: split = os.path.split(commercial_filename) sound_file = os.path.join(os.path.split(split[0])[1], split[1]) smb_path = self.CIFS_HEADER_COMMERCIALS + sound_file self.logger.info("commercial : Playing %s, %s, %s" % (sound_file, smb_path, cgroups)) if self.doorbell_thread: if self.doorbell_thread.isAlive(): self.logger.info( "Dropping commercial because it's already playing") continue if cgroups is None: cgroups = [] if cgroups: sfx = "-Z" else: sfx = None if vol: volume = vol else: volume = self.cfg.sonos_doorbell.commercials.volume # override if self.cfg.sonos_doorbell.commercials.volume_override: volume = self.cfg.sonos_doorbell.commercials.volume self.doorbell_thread = self.sonosPoolManager.pause_play_bell_resume( smb_path, volume, groups=cgroups, splitter=sfx, ignore_sonos_names_enabled=False) self.logger.info("starting thread %s " % self.doorbell_thread) self.doorbell_thread.start() self.doorbell_thread.join() else: self.logger.error("No Commercials found") if not self.commercial_thread_event.isSet(): self.logger.info("Commercial. Waiting for %s min." % frequency) self.commercial_thread_event.wait(60 * frequency) self.logger.info("Commercial. Thread end.") def run(self): self.socket_receiver_thread = Thread(target=self.socket_receiver, args=()) self.socket_receiver_thread.setDaemon(True) self.socket_receiver_thread.start() # Threads shouldn't stop self.socket_receiver_thread.join() def socket_receiver(self): self.logger.info("Started listening on UDP port %s" % UDP_PORT) # t = None while True: data, addr = self.sock.recvfrom(UDP_PORT) # drop the packet if there is a bell in progress. # if t: # if t.isAlive(): # self.logger.info("Dropping packet: bell in progress.") # continue try: ret = parseExtensionProtocol(data.decode("utf-8"), str(self.cfg)) if ret: if ret["type"] in "GeneralFeatures_Alarm": if ret["command"] == 1: # start sound = ret["params"][0] volume = ret["params"][1] # Calculate volume percentage from protocol input. # volume = volume * 11 # [0,9] => [0-100] # Search for the sound in uploads and fallback to defaults. if self.cfg.sonos_doorbell.default_sound != 0: sound = self.cfg.sonos_doorbell.default_sound default_sound = True if "default sound" != self.cfg.sonos_doorbell.sounds_filelist[ sound - 1]: default_sound = False sound_file = self.soundsManager.search_path_by_index( sound, default_sound) if default_sound: if sound_file: split = os.path.split(sound_file) sound_file = os.path.join( os.path.split(split[0])[1], split[1]) smb_path = self.CIFS_HEADER + sound_file self.logger.info( "Alarm : Playing %s, %s, %s." % (sound_file, smb_path, volume)) self.logger.info("Alarm : Start") # stop doorbell thread if it is running, # other commands do not use threading and long processes if self.doorbell_thread: if self.doorbell_thread.isAlive(): self.doorbell_thread.stop() self.doorbell_thread.join() # start alarm self.alarm_thread = self.sonosPoolManager.alarm_start( smb_path, volume) self.alarm_thread.start() else: self.logger.error( "Couldn't locate a sound @ index : %s" % sound) else: if sound_file: split = os.path.split(sound_file) sound_file = os.path.join( os.path.split(split[0])[1], split[1]) smb_path = self.CIFS_HEADER_CUSTOM + sound_file self.logger.info( "Alarm : Playing %s, %s, %s." % (sound_file, smb_path, volume)) self.logger.info("Alarm : Start") # stop doorbell thread if it is running, # other commands do not use threading and long processes if self.doorbell_thread: if self.doorbell_thread.isAlive(): self.doorbell_thread.stop() self.doorbell_thread.join() # start alarm self.alarm_thread = self.sonosPoolManager.alarm_start( smb_path, volume) self.alarm_thread.start() else: self.logger.error( "Couldn't locate a sound @ index : %s" % sound) else: # stop self.logger.info("Alarm : Stop") if self.alarm_thread: if self.alarm_thread.isAlive(): self.alarm_thread.stop() self.alarm_thread.join() self.sonosPoolManager.alarm_stop() else: if self.alarm_thread: if self.alarm_thread.isAlive(): self.logger.info( "Dropping other commands. Alarm is in progress." ) continue if ret["type"] in "GeneralFeatures_LineIn": if ret["command"] == 1: volume = ret["params"][0] self.sonosPoolManager.annoucementStart(volume) self.announcementStarted = True else: if self.announcementStarted: self.sonosPoolManager.annoucementStop() self.announcementStarted = False elif ret["type"] in "GeneralFeatures_DoorBell": if self.cfg.sonos_doorbell.enable: sound = ret["params"][0] volume = ret["params"][1] # Calculate volume percentage from protocol input. # volume = volume * 11 # [0,9] => [0-100] # Apply configured volume override. if self.cfg.sonos_doorbell.volume_override: volume = self.cfg.sonos_doorbell.volume # Search for the sound in uploads and fallback to defaults. if self.cfg.sonos_doorbell.default_sound != 0: sound = self.cfg.sonos_doorbell.default_sound default_sound = True if "default sound" != self.cfg.sonos_doorbell.sounds_filelist[ sound - 1]: default_sound = False sound_file = self.soundsManager.search_path_by_index( sound, default_sound) if default_sound: if sound_file: split = os.path.split(sound_file) sound_file = os.path.join( os.path.split(split[0])[1], split[1]) smb_path = self.CIFS_HEADER + sound_file self.logger.info( "Playing %s, %s, %s." % (sound_file, smb_path, volume)) # create sonos list from active groups. # self.logger.info("Groups: %s, %s, %s." % (group1, group2, group3)) if self.doorbell_thread: if self.doorbell_thread.isAlive(): self.logger.info( "Dropping doorbell because it's already playing" ) continue self.doorbell_thread = self.sonosPoolManager.pause_play_bell_resume( smb_path, volume, suffix="-DB", ignore_sonos_names_enabled=self.cfg .sonos_doorbell.ignore_sonos_names) self.doorbell_thread.start() else: self.logger.error( "Couldn't locate a sound @ index : %s" % sound) else: if sound_file: split = os.path.split(sound_file) sound_file = os.path.join( os.path.split(split[0])[1], split[1]) smb_path = self.CIFS_HEADER_CUSTOM + sound_file self.logger.info( "Playing %s, %s, %s." % (sound_file, smb_path, volume)) # create sonos list from active groups. # self.logger.info("Groups: %s, %s, %s." % (group1, group2, group3)) if self.doorbell_thread: if self.doorbell_thread.isAlive(): self.logger.info( "Dropping doorbell because it's already playing" ) continue self.doorbell_thread = self.sonosPoolManager.pause_play_bell_resume( smb_path, volume, suffix="-DB", ignore_sonos_names_enabled=self.cfg .sonos_doorbell.ignore_sonos_names) self.doorbell_thread.start() else: self.logger.error( "Couldn't locate a sound @ index : %s" % sound) else: self.logger.info( "SonosDoorbell feature disabled !") elif ret["type"] in "AllSpeakersFeatures_Play": command = ret["command"] volume = None if command is 2: volume = ret["volume"][0] saved_radio = self.cfg.sonos_doorbell.saved_radios[ 0] self.sonosPoolManager.play_track_or_radio( ruri=saved_radio.url, rmeta=saved_radio.meta, command=command, volume=volume) elif ret["type"] in "AllSpeakersFeatures_Pause": self.sonosPoolManager.pause() elif ret["type"] in "AllSpeakersFeatures_VolUp": command = ret["command"] percentage = 0 if command == 1: percentage = 1 elif command == 2: percentage = 5 self.sonosPoolManager.volup(percentage=percentage) elif ret["type"] in "AllSpeakersFeatures_VolDown": command = ret["command"] percentage = 0 if command == 1: percentage = 1 elif command == 2: percentage = 5 self.sonosPoolManager.voldown( percentage=percentage) elif ret["type"] in "AllSpeakersFeatures_TrackUp": self.sonosPoolManager.previous() elif ret["type"] in "AllSpeakersFeatures_TrackDown": self.sonosPoolManager.next() elif ret[ "type"] in "AllSpeakersFeatures_NextRadioStation": self.sonosPoolManager.nextRadio() elif ret[ "type"] in "AllSpeakersFeatures_PreviousRadioStation": self.sonosPoolManager.previousRadio() elif ret["type"] in "AllSpeakersFeatures_SetVolume": try: volume = int(ret["volume"][0]) self.sonosPoolManager.volset(volume) except: self.logger.error( "SetVolume protocol error %s" % ret["params"]) elif ret[ "type"] in "AllSpeakersFeatures_SetRadioStation": try: station = int(ret["station"][0]) saved_radio = self.cfg.sonos_doorbell.saved_radios[ station] if saved_radio.url: self.sonosPoolManager.play_uri( saved_radio.url, saved_radio.meta) except: self.logger.error( "SetRadio protocol error %s" % ret["station"]) elif ret[ "type"] in "AllSpeakersFeatures_Commercials_Enable": try: self.logger.info( "Commercials Enable protocol.") if self.commercial_thread: if self.commercial_thread.isAlive(): self.logger.info( "Commercial is already enabled") continue self.commercial_thread_event.clear() self.commercial_thread = threading.Thread( target=self.commercials_loop, args=(ret["params"][0], None, [])) self.commercial_thread.start() except: self.logger.error("Commercials Enable error.") elif ret[ "type"] in "AllSpeakersFeatures_Commercials_Disable": try: self.logger.info( "Commercials Disable protocol.") self.commercial_thread_event.set() except: self.logger.error("Commercials Disable error.") elif ret["type"] in "ZonesFeatures_Commercials_Enable": try: self.logger.info( "Commercials Enable protocol.") if self.commercial_thread: if self.commercial_thread.isAlive(): self.logger.info( "Commercial is already enabled") continue self.commercial_thread_event.clear() self.commercial_thread = threading.Thread( target=self.commercials_loop, args=(ret["params"][1], None, ret["params"][0])) self.commercial_thread.start() except: self.logger.error("Commercials Enable error.") elif ret[ "type"] in "ZonesFeatures_Commercials_Disable": try: self.logger.info( "Commercials Disable protocol.") self.commercial_thread_event.set() except: self.logger.error("Commercials Disable error.") elif ret["type"] in "ZonesFeatures_Play": command = ret["command"] saved_radio = self.cfg.sonos_doorbell.saved_radios[ 0] if command is 1: self.sonosPoolManager.play_track_or_radio( groups=ret["params"], splitter="-Z", ruri=saved_radio.url, rmeta=saved_radio.meta, command=command) elif command is 2: for i in range(1, 4, 2): volume = ret["params"][i] zone = [ret["params"][i - 1]] self.sonosPoolManager.play_track_or_radio( groups=zone, splitter="-Z", ruri=saved_radio.url, rmeta=saved_radio.meta, command=command, volume=volume) elif ret["type"] in "ZonesFeatures_Pause": self.sonosPoolManager.pause(ret["params"], "-Z") elif ret["type"] in "ZonesFeatures_VolUp": command = ret["command"] percentage = 0 if command == 1: percentage = 1 elif command == 2: percentage = 5 self.sonosPoolManager.volup(ret["params"], "-Z", percentage=percentage) elif ret["type"] in "ZonesFeatures_VolDown": command = ret["command"] percentage = 0 if command == 1: percentage = 1 elif command == 2: percentage = 5 self.sonosPoolManager.voldown( ret["params"], "-Z", percentage=percentage) elif ret["type"] in "ZonesFeatures_TrackUp": self.sonosPoolManager.previous(ret["params"], "-Z") elif ret["type"] in "ZonesFeatures_TrackDown": self.sonosPoolManager.next(ret["params"], "-Z") elif ret["type"] in "ZonesFeatures_NextRadioStation": self.sonosPoolManager.nextRadio( ret["params"], "-Z") elif ret[ "type"] in "ZonesFeatures_PreviousRadioStation": self.sonosPoolManager.previousRadio( ret["params"], "-Z") elif ret["type"] in "ZonesFeatures_SetVolume": self.sonosPoolManager.volset( ret["params"][1], [ret["params"][0]], "-Z") elif ret["type"] in "ZonesFeatures_SetRadioStation": try: saved_radio = self.cfg.sonos_doorbell.saved_radios[ ret["params"][1]] if saved_radio.url: self.sonosPoolManager.play_uri( saved_radio.url, saved_radio.meta, [ret["params"][0]], "-Z") except: self.logger.error( "SetRadio protocol error %s" % ret["station"]) else: self.logger.error("Packet type not known : %s" % ret["type"]) else: self.logger.warning("Can't decode this packet : %s" % data) except: self.logger.error("In main loop : ", exc_info=True)
class LextendEngine(object): def __init__(self, logger=None): self.logger = logger or logging.getLogger(__name__) self.logger.info("Starting Lextend Engine.") # Load configuration from XML file try: local_ip = get_local_ip() except: local_ip = "" self.cfg = ConfigManager(CONFIGURATION_SUBDIRECTORY, CONFIGURATION_FILENAME, lextend_ip = local_ip) # Create a sonos manager self.sonosPoolManager = SonosPoolManager() try: self.sonosPoolManager.discover() except: self.logger.error("Could not start discovering Sonos.") # Create a sounds manager self.soundsManager = SoundsManager(CONFIGURATION_SUBDIRECTORY) # Expose some RPC interfaces for webfrontend self.rpc_server_thread = RPC_Server(self.sonosPoolManager) self.rpc_server_thread.start() # Get aria ip address while True: Lextend_ip_address = get_local_ip() if Lextend_ip_address == "": self.logger.error("FATAL: Cannot get my IP address. Retrying in 10s.") sleep(10) else: self.logger.info("Detected IP address : %s" % Lextend_ip_address) break self.CIFS_HEADER = "x-file-cifs://" + Lextend_ip_address + "/sonos_share/" # Start listening to miniserver. self.sock = None while self.sock == None: try: self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock.bind(("", UDP_PORT)) except Exception: # NOTE : the common error is : 98 : address already in use. self.logger.error("Failed to create socket, retrying in 10 seconds.", exc_info=True) sleep(10) def run(self): self.socket_receiver_thread = Thread(target=self.socket_receiver, args=()) self.socket_receiver_thread.setDaemon(True) self.socket_receiver_thread.start() # Threads shouldn't stop self.socket_receiver_thread.join() def socket_receiver(self): self.logger.info("Started listening on UDP port %s" % UDP_PORT) while True: data, addr = self.sock.recvfrom(UDP_PORT) try: ret = parseExtensionProtocol(data, self.cfg) if ret: if ret["type"] in "sonos_doorbell": if self.cfg.sonos_doorbell.enable: sound = ret["params"][0] volume = ret["params"][1] # Calculate volume percentage from protocol input. volume = volume * 11 # [0,9] => [0-100] # Apply configured volume override. if self.cfg.sonos_doorbell.volume_override: volume = self.cfg.sonos_doorbell.volume # Search for the sound in uploads and fallback to defaults. if self.cfg.sonos_doorbell.default_sound != 0: sound = self.cfg.sonos_doorbell.default_sound default_sound=True if "default sound" != self.cfg.sonos_doorbell.sounds_filelist[sound-1]: default_sound=False sound_file = self.soundsManager.search_path_by_index(sound, default_sound) if sound_file: split = os.path.split(sound_file) sound_file = os.path.join(os.path.split(split[0])[1], split[1]) smb_path = self.CIFS_HEADER + sound_file self.logger.info("Playing %s, %s, %s." % (sound_file, smb_path, volume)) self.sonosPoolManager.pause_play_bell_resume(smb_path, volume) else: self.logger.error("Couldn't locate a sound @ index : %s" % sound) else: self.logger.info("SonosDoorbell feature disabled !") else: self.logger.error("Packet type not known : %s" % ret["type"]) else: self.logger.warn("Can't decode this packet : %s" % data) except: self.logger.error("In main loop : ", exc_info=True)