diff --git a/main.py b/main.py deleted file mode 100644 index c188d91..0000000 --- a/main.py +++ /dev/null @@ -1,372 +0,0 @@ -import asyncore -import logging -import json -import time -from datetime import datetime - -import Resources.CONSTANTS as CONST -import Resources.MLCONFIG as MLCONFIG -import Resources.MLGW_CLIENT as MLGW -import Resources.MLCLI_CLIENT as MLCLI -import Resources.BLHIP_CLIENT as BLHIP -import Resources.MLtn_CLIENT as MLtn -import Resources.ASBridge as ASBridge - - -if __name__ == '__main__': - - # Define callback function for JSON data dump from B&O Gateway - def cb(name, header, payload, message): - # Sanitise messages - # Check for standby conditions - try: - if message['State_Update']['state'] == '': - message['State_Update']['state'] = 'Standby' - except KeyError: - pass - try: - if message['State_Update']['source'] == '': - message['State_Update']['state'] = 'Standby' - except KeyError: - pass - # Sanitise unknown Channel/Tracks - try: - if message['State_Update']['nowPlayingDetails']['channel_track'] in [0, 255, '0', '255']: - del(message['State_Update']['nowPlayingDetails']['channel_track']) - except KeyError: - pass - try: - if message['State_Update']['source'] == "A.TAPE2/N.MUSIC": - del(message['State_Update']['nowPlayingDetails']['channel_track']) - except KeyError: - pass - - try: # Transport controls for N.MUSIC iTunes control - if message['State_Update']['source'] == "A.TAPE2/N.MUSIC": - CONST.gateway['Current Audio Source'] = "A.TAPE2/N.MUSIC" - try: - if message['Device'] not in CONST.gateway['N.MUSIC Renderers']: - CONST.gateway['N.MUSIC Renderers'].append(message['Device']) - finally: - iTunes_transport_control(message) - - elif message['State_Update']['source'] != "A.TAPE2/N.MUSIC": - if message['State_Update']['source'] in CONST.source_type_dict.get("Audio Sources"): - CONST.gateway['Now Playing'] = 'Unknown' - CONST.gateway['N.MUSIC Renderers'] = [] - except KeyError: - pass - - # If any AV renderers are playing N.Music, provide an update of current track - if CONST.gateway['N.MUSIC Renderers'] and CONST.gateway['Current Audio Source'] == "A.TAPE2/N.MUSIC": - _get_track_info(message) - - # If State Update received then post update in Notification Center - notify_and_update(message) - - # when count of N.Music renderers is zero, stop iTunes playback - if not CONST.gateway['N.MUSIC Renderers']: - iTunes.stop() - - # Report message content to log - message_log(name, header, payload, message) - - def notify_and_update(message): - # Post Source change information to Notification Center - if 'State_Update' in message and 'source' in message['State_Update']: - current_source = str(message['State_Update']['source']) - - # Check to see if this is a new Music Source notification - if current_source in CONST.source_type_dict.get("Audio Sources"): - update_devices(message, current_source, 'Audio Source') - if current_source not in ['', CONST.gateway['Current Audio Source']]: - CONST.gateway['Current Audio Source'] = current_source - iTunes.notify("Current Audio Source: " + str(message['State_Update']['sourceName']), - "Audio Source Update") - - # Check to see if this is a new Video Source notification - elif current_source in CONST.source_type_dict.get("Video Sources"): - update_devices(message, current_source, 'Video Source') - if current_source not in ['', CONST.gateway['Current Video Source']]: - CONST.gateway['Current Video Source'] = current_source - iTunes.notify("Current Video Source: " + str(message['State_Update']['sourceName']), - "Video Source Update") - - # If source is not in Audio or Video dictionaries then it's a standby message - else: - update_devices(message, current_source, 'No Source') - elif 'State_Update' in message and 'state' in message['State_Update']: - update_devices(message, 'No Source', 'No Source') - - def update_devices(message, current_source, source_type): - # Loop over devices to find device sending update message - for device in CONST.devices: - try: - if str(message['Device']) == device['Device']: - # Filter for device status update after entering standby - - # not sure why this happens but it does sometimes - if device['State'] == 'Standby' and time.time() - device['last update'] < 1.0: - return - - # If in standby, set current source to None - if message['State_Update']['state'] == 'Standby': - if device['State'] != 'Standby': - iTunes.notify(str(device['Device']) + " is now on standby", - "System Standby") - device['State'] = 'Standby' - if message['Device'] in CONST.gateway['N.MUSIC Renderers']: - CONST.gateway['N.MUSIC Renderers'].remove(message['Device']) - device['Now Playing'] = 'None' - device['Current Source'] = 'None' - device['last update'] = time.time() - device['Channel/Track'] = '0' - try: - message['State_Update']['nowPlaying'] = 'None' - except KeyError: - pass - - # Report player state and source - elif message['State_Update']['state'] in ["None", "Play"]: - if message['State_Update']['nowPlaying'] not in ['', 'Unknown'] and \ - message['State_Update']['nowPlaying'] != device['Now Playing']: - iTunes.notify(str(device['Device']) + " now playing " + - str(message['State_Update']['nowPlaying']) + " from source " + - str(message['State_Update']['sourceName']), - "Now Playing Update") - # Update device data - device['Now Playing'] = message['State_Update']['nowPlaying'] - CONST.gateway['Now Playing'] = device['Now Playing'] - device['Channel/Track'] = 'NA' - device['last update'] = time.time() - - elif 'nowPlayingDetails' in message['State_Update'] and \ - message['State_Update']['nowPlaying'] in ['', 'Unknown'] and \ - message['State_Update']['nowPlayingDetails']['channel_track'] != \ - device['Channel/Track']: - if source_type == "Audio Source": - iTunes.notify(str(device['Device']) + " now playing track " + - str(message['State_Update']['nowPlayingDetails']['channel_track']) + - " from source " + str(message['State_Update']['sourceName']), - "Now Playing Update") - elif source_type == "Video Source": - iTunes.notify(str(device['Device']) + " now playing channel " + - str(message['State_Update']['nowPlayingDetails']['channel_track']) + - " from source " + str(message['State_Update']['sourceName']), - "Now Playing Update") - # Update device data - device['Now Playing'] = 'Unknown' - device['Channel/Track'] = message['State_Update']['nowPlayingDetails']['channel_track'] - device['last update'] = time.time() - - elif message['State_Update']['state'] != device['State']: - iTunes.notify(str(device['Device']) + " is now playing " + - str(message['State_Update']['sourceName']), - "Source Update") - # Update device data - device['Now Playing'] = "Unknown" - device['Channel/Track'] = 'NA' - device['last update'] = time.time() - - # Update the state of the device - device['Current Source'] = current_source - device['Current Source Type'] = source_type - device['State'] = message['State_Update']['state'] - except KeyError: - pass - - # Now loop over devices and update any playing an audio source with the new distributed - # source (the masterlink network only supports a single audio source for distribution) - try: - if source_type == "Audio Source": - if device['Current Source'] in CONST.source_type_dict.get("Audio Sources"): - device['Current Source'] = current_source - device['last update'] = time.time() - except KeyError: - pass - - logging.debug(json.dumps(device, indent=4)) - - def iTunes_transport_control(message): - try: # If N.MUSIC command, trigger appropriate iTunes control - if message['State_Update']['state'] not in ["", "Standby"]: - iTunes.play() - except KeyError: - pass - - try: # If N.MUSIC selected and Beo4 command received then run appropriate transport commands - if message['State_Update']['command'] == "Go/Play": - iTunes.play() - - elif message['State_Update']['command'] == "Stop": - iTunes.pause() - - elif message['State_Update']['command'] == "Exit": - iTunes.stop() - - elif message['State_Update']['command'] == "Step Up": - iTunes.next_track() - - elif message['State_Update']['command'] == "Step Down": - iTunes.previous_track() - - elif message['State_Update']['command'] == "Wind": - iTunes.wind(10) - - elif message['State_Update']['command'] == "Rewind": - iTunes.rewind(-10) - - elif message['State_Update']['command'] == "Shift-1/Random": - iTunes.shuffle() - - # If 'Info' pressed - update track info - elif message['State_Update']['command'] == "Info": - _get_track_info(message) - - # If colour key pressed, execute the appropriate applescript - elif message['State_Update']['command'] == "Green": - # Play a specific playlist - defaults to Recently Played - script = ASBridge.__file__[:-12] + 'Scripts/green.scpt' - iTunes.run_script(script) - - elif message['State_Update']['command'] == "Yellow": - # Play a specific playlist - defaults to Recently Added - script = ASBridge.__file__[:-12] + 'Scripts/yellow.scpt' - iTunes.run_script(script) - - elif message['State_Update']['command'] == "Blue": - # Play the current album - script = ASBridge.__file__[:-12] + 'Scripts/blue.scpt' - iTunes.run_script(script) - - elif message['State_Update']['command'] in ["0xf2", "Red", "MOTS"]: - # More of the same (start a playlist with just current track and let autoplay find similar tunes) - script = ASBridge.__file__[:-12] + 'Scripts/red.scpt' - iTunes.run_script(script) - except KeyError: - pass - - def _get_track_info(message): - track_info = iTunes.get_current_track_info() - if track_info[0] not in [None, 'None']: - track_info = track_info[0] + "' by " + track_info[2] + " from the album '" + track_info[1] + "'" - if 'Type' in message and \ - message['Type'] == "AV RENDERER" and 'nowPlaying' in message['State_Update']: - message['State_Update']['nowPlaying'] = track_info - - if track_info != CONST.gateway['Now Playing']: - CONST.gateway['Now Playing'] = track_info - logging.info("iTUNES CURRENT TRACK INFO:\n\tNow playing: " + track_info + "\n") - if 'Device' in message and message['Type'] == "AV RENDERER ": - iTunes.notify(str(message['Device']) + ' now playing ' + track_info, - "Now Playing Update") - else: - iTunes.notify("iTunes now playing " + track_info, - "Now Playing Update") - - def message_log(name, header, payload, message): - # Pretty formatting - convert to JSON format then remove braces - message = json.dumps(message, indent=4) - for r in (('"', ''), (',', ''), ('{', ''), ('}', '')): - message = str(message).replace(*r) - - # Print message data - if len(payload) + 9 < 73: - logging.info("\n\t----------------------------------------------------------------------------" + - "\n\t" + name + ": <--Data-received!-<< " + - datetime.now().strftime("on %d/%m/%y at %H:%M:%S") + - "\n\t----------------------------------------------------------------------------" + - "\n\tHeader: " + header + - "\n\tPayload: " + payload + - "\n\t============================================================================" + - message) - elif 73 < len(payload) + 9 < 137: - logging.info("\n\t----------------------------------------------------------------------------" + - "\n\t" + name + ": <--Data-received!-<< " + - datetime.now().strftime("on %d/%m/%y at %H:%M:%S") + - "\n\t----------------------------------------------------------------------------" + - "\n\tHeader: " + header + - "\n\tPayload: " + payload[:66] + "\n\t\t" + payload[66:137] + - "\n\t============================================================================" + - message) - else: - logging.info("\n\t----------------------------------------------------------------------------" + - "\n\t" + name + ": <--Data-received!-<< " + - datetime.now().strftime("on %d/%m/%y at %H:%M:%S") + - "\n\t----------------------------------------------------------------------------" + - "\n\tHeader: " + header + - "\n\tPayload: " + payload[:66] + "\n\t\t" + payload[66:137] + "\n\t\t" + payload[137:] + - "\n\t============================================================================" + - message) - - def check_connection(self): - last = round(time.time() - self.last_received_at, 2) - logging.debug(self.name + " Protocol Client last received " + str(last) + " seconds ago") - # Recconect if socket has disconnected, or if no response received to last ping - if not self.is_connected or last > 60: - logging.info("\t" + self.name + ": Reconnecting!") - self.handle_close() - time.sleep(0.5) - self.client_connect() - - # ######################################################################################## - # Main program loop - host = 'blgw.local' # Host address of gateway - port = [9000, # MLGW protocol port - default 9000 - 9100, # BLGW Home Integration Protocol port - default 9100 - 23] # Telnet port - default 23 - user = 'admin' # User name - default is admin - pwd = 'admin' # password - default is admin - - # Instantiate an AppleScriptBridge MusicController for N.MUSIC control of apple Music - iTunes = ASBridge.MusicController() - - logging.basicConfig(level=logging.INFO) - config = MLCONFIG.MLConfig(host, user, pwd) - - # Create MLGW Protocol and ML_CLI Protocol clients (basic command listening) - logging.info('Creating MLGW Protocol Client...') - mlgw = MLGW.MLGWClient(host, port[0], user, pwd, 'MLGW protocol', cb) - asyncore.loop(count=10, timeout=0.2) - - logging.info('Creating ML Command Line Protocol Client...') - mlcli = MLCLI.MLCLIClient(host, port[2], user, pwd, 'ML command line interface', cb) - # Log onto the MLCLI client and ascertain the gateway model - asyncore.loop(count=10, timeout=0.2) - - # Now MLGW and MasterLink Command Line Client are set up, retrieve MasterLink IDs of products - config.get_masterlink_id(mlgw, mlcli) - - # If the gateway is a BLGW use the BLHIP protocol, else use the legacy MLHIP protocol - if mlcli.isBLGW: - logging.info('Creating BLGW Home Integration Protocol Client...') - blgw = BLHIP.BLHIPClient(host, port[1], user, pwd, 'BLGW Home Integration Protocol', cb) - mltn = None - else: - logging.info('Creating MLGW Home Integration Protocol Client...') - mltn = MLtn.MLtnClient(host, port[2], user, pwd, 'ML telnet client', cb) - blgw = None - - # Main program loop - while True: - # Ping all connections every 10 minutes to prompt messages on the network - asyncore.loop(count=595, timeout=1) - if mlgw.is_connected: - mlgw.ping() - if mlcli.is_connected: - mlcli.ping() - if mlcli.isBLGW: - if blgw.is_connected: - blgw.ping() - else: - if mltn.is_connected: - mltn.ping() - - # Check the connections approximately every 10 minutes to keep sockets open - asyncore.loop(count=5, timeout=1) - logging.debug("LOOP: %d enqueued, waiting to finish!" % len(asyncore.socket_map)) - check_connection(mlgw) - check_connection(mlcli) - if mlcli.isBLGW: - check_connection(blgw) - else: - check_connection(mltn)