mirror of
https://github.com/LukeSpad/BeoGateway.git
synced 2024-12-23 21:51:51 +00:00
Add files via upload
This commit is contained in:
parent
d58e5a0c77
commit
fded45c031
4 changed files with 65 additions and 22 deletions
|
@ -1,13 +1,14 @@
|
|||
#!/usr/bin/python
|
||||
import os
|
||||
import unicodedata
|
||||
from Foundation import NSAppleScript
|
||||
from ScriptingBridge import SBApplication
|
||||
|
||||
''' Module defining a MusicController class for Apple Music, enables:
|
||||
basic transport control of the player,
|
||||
query of player status,
|
||||
playing existing playlists, and
|
||||
running external appleScripts for more complex control
|
||||
playing existing playlists,
|
||||
running external appleScripts for more complex control, and
|
||||
reporting messages in the notification centre'''
|
||||
|
||||
PLAYSTATE = dict([
|
||||
|
@ -26,9 +27,14 @@ class MusicController(object):
|
|||
|
||||
# Player information
|
||||
def get_current_track_info(self):
|
||||
name = str(self.app.currentTrack().name())
|
||||
album = str(self.app.currentTrack().album())
|
||||
artist = str(self.app.currentTrack().artist())
|
||||
name = self.app.currentTrack().name()
|
||||
album = self.app.currentTrack().album()
|
||||
artist = self.app.currentTrack().artist()
|
||||
if name:
|
||||
# Deal with tracks with non-ascii characters such as accents
|
||||
name = unicodedata.normalize('NFD', name).encode('ascii', 'ignore')
|
||||
album = unicodedata.normalize('NFD', album).encode('ascii', 'ignore')
|
||||
artist = unicodedata.normalize('NFD', artist).encode('ascii', 'ignore')
|
||||
return [name, album, artist]
|
||||
|
||||
def get_current_play_state(self):
|
||||
|
@ -48,6 +54,7 @@ class MusicController(object):
|
|||
elif PLAYSTATE.get(self.app.playerState()) == 'Pause':
|
||||
self.app.playpause()
|
||||
elif PLAYSTATE.get(self.app.playerState()) == 'Stop':
|
||||
self. app.setValue_forKey_('true', 'shuffleEnabled')
|
||||
playlist = self.app.sources().objectWithName_("Library")
|
||||
playlist.playOnce_(None)
|
||||
|
||||
|
@ -79,6 +86,12 @@ class MusicController(object):
|
|||
|
||||
# ########################################################################################
|
||||
# More complex playback control functions
|
||||
def shuffle(self):
|
||||
if self.app.shuffleEnabled():
|
||||
self.app.setValue_forKey_('false', 'shuffleEnabled')
|
||||
else:
|
||||
self.app.setValue_forKey_('true', 'shuffleEnabled')
|
||||
|
||||
def set_current_track_position(self, time, mode='Relative'):
|
||||
if mode == 'Relative':
|
||||
# Set playback position in seconds relative to current position
|
||||
|
@ -102,10 +115,10 @@ class MusicController(object):
|
|||
s.executeAndReturnError_(None)
|
||||
|
||||
@staticmethod
|
||||
def notify(message):
|
||||
def notify(message, subtitle):
|
||||
# Post message in notification center
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
script = 'tell application "' + path + '/Notify.app" to notify("BeoGateway", "' + \
|
||||
message + '")'
|
||||
message + '", "' + subtitle + '")'
|
||||
s = NSAppleScript.alloc().initWithSource_(script)
|
||||
s.executeAndReturnError_(None)
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
# Constants for B&O telegram protocols
|
||||
# ########################################################################################
|
||||
# Config data (set on initialisation)
|
||||
gateway = dict([("Current Audio Source", ''), ("Current Video Source", '')])
|
||||
gateway = dict(
|
||||
[
|
||||
("Current Audio Source", 'Unknown'),
|
||||
("Current Video Source", 'Unknown'),
|
||||
("Now Playing", 'Unknown'),
|
||||
("N.MUSIC Renderers", [])
|
||||
]
|
||||
)
|
||||
rooms = []
|
||||
devices = []
|
||||
available_sources = []
|
||||
|
@ -13,7 +20,7 @@ source_type_dict = dict(
|
|||
[
|
||||
("Video Sources", ("TV", "V.AUX/DTV2", "MEDIA", "V.TAPE/V.MEM/DVD2", "DVD", "CAMERA",
|
||||
"SAT/DTV", "PC", "WEB", "DOORCAM", "PHOTO", "USB2", "WEBMEDIA", "AV.IN",
|
||||
"HOMEMEDIA", "DNLA", "RECORDINGS", "CAMERA", "USB", "DNLA-DMR", "YOUTUBE",
|
||||
"HOMEMEDIA", "DVB_RADIO", "DNLA", "RECORDINGS", "CAMERA", "USB", "DNLA-DMR", "YOUTUBE",
|
||||
"HOME.APP", "HDMI_1", "HDMI_2", "HDMI_3", "HDMI_4", "HDMI_5", "HDMI_6",
|
||||
"HDMI_7", "HDMI_8", "MATRIX_1", "MATRIX_2", "MATRIX_3", "MATRIX_4", "MATRIX_5",
|
||||
"MATRIX_6", "MATRIX_7", "MATRIX_8", "MATRIX_9", "MATRIX_10", "MATRIX_11",
|
||||
|
@ -262,9 +269,9 @@ ml_command_type_dict = dict(
|
|||
(0x3C, "TIMER"),
|
||||
(0x40, "CLOCK"),
|
||||
(0x44, "TRACK_INFO"),
|
||||
# LOCKmANAGER_COMMAND: Lock to Determine what device issues source commands
|
||||
# reference: https://tidsskrift.dk/daimipb/article/download/7043/6004/0
|
||||
(0x45, "GOTO_SOURCE"),
|
||||
# LOCKMANAGER_COMMAND: Lock to Determine what device issues source commands
|
||||
# reference: https://tidsskrift.dk/daimipb/article/download/7043/6004/0
|
||||
(0x5C, "LOCK_MANAGER_COMMAND"),
|
||||
(0x6C, "DISTRIBUTION_REQUEST"),
|
||||
(0x82, "TRACK_INFO_LONG"),
|
||||
|
@ -301,7 +308,8 @@ ml_command_type_dict = dict(
|
|||
# On power up all devices send out a request key telegram. If
|
||||
# no lock manager is allocated the devices send out a key_lost telegram. The Video Master (or Power
|
||||
# Master in older implementations) then asserts a NEW_LOCKmANAGER telegram and assumes responsibility
|
||||
# for LOCKmANAGER_COMMAND telegrams until a key transfer occurs.
|
||||
# for LOCKMANAGER_COMMAND telegrams until a key transfer occurs.
|
||||
# reference: https://tidsskrift.dk/daimipb/article/download/7043/6004/0
|
||||
(0x12, "KEY_LOST"), # ?
|
||||
# Unknown command with payload of length 1.
|
||||
# bit 0: unknown
|
||||
|
@ -344,9 +352,11 @@ ml_device_dict = dict(
|
|||
(0x83, "ALL LINK DEVICES"),
|
||||
(0x80, "ALL"),
|
||||
(0xF0, "MLGW"),
|
||||
(0x21, "BLC NL/ML"),
|
||||
# Power Master exists in older (pre 1996?) ML implementations. Later revisions enforced the Video Master
|
||||
# as lock key manager for the system and the concept was phased out. If your system is older than 2000
|
||||
# you may see this device type on the network.
|
||||
# reference: https://tidsskrift.dk/daimipb/article/download/7043/6004/0
|
||||
(0xFF, "POWER MASTER"), # ?
|
||||
]
|
||||
)
|
||||
|
@ -433,7 +443,7 @@ destselectordict = dict(
|
|||
[
|
||||
(0x00, "Video Source"),
|
||||
(0x01, "Audio Source"),
|
||||
(0x05, "V.TAPE/V.MEM"),
|
||||
(0x05, "Secondary Video Source (V.TAPE/V.MEM)"),
|
||||
(0x0F, "All Products"),
|
||||
(0x1B, "MLGW"),
|
||||
]
|
||||
|
@ -451,9 +461,9 @@ mlgw_soundstatusdict = dict([(0x00, "Not muted"), (0x01, "Muted")])
|
|||
mlgw_speakermodedict = dict(
|
||||
[
|
||||
(0x01, "Center channel"),
|
||||
(0x02, "2ch stereo"),
|
||||
(0x02, "2 channel stereo"),
|
||||
(0x03, "Front surround"),
|
||||
(0x04, "4ch stereo"),
|
||||
(0x04, "4 channel stereo"),
|
||||
(0x05, "Full surround"),
|
||||
(0xFD, "<all>"), # Dummy for 'Listen for all modes'
|
||||
]
|
||||
|
|
|
@ -193,7 +193,10 @@ class MLCLIClient(asynchat.async_chat):
|
|||
# Decode header
|
||||
message = OrderedDict()
|
||||
self._get_device_info(message, telegram)
|
||||
message["from_device"] = self._get_device_name(self._dictsanitize(CONST.ml_device_dict, telegram[1]))
|
||||
if 'Device' not in message:
|
||||
# If ML telegram has been matched to a Masterlink node in the devices list then the 'from_device'
|
||||
# key is redundant - it will always be identical to the 'Device' key
|
||||
message["from_device"] = self._dictsanitize(CONST.ml_device_dict, telegram[1])
|
||||
message["from_source"] = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[5])
|
||||
message["to_device"] = self._get_device_name(self._dictsanitize(CONST.ml_device_dict, telegram[0]))
|
||||
message["to_source"] = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[4])
|
||||
|
@ -252,6 +255,10 @@ class MLCLIClient(asynchat.async_chat):
|
|||
message["State_Update"]["sourceID"] = telegram[10]
|
||||
message["State_Update"]["source_type"] = self._get_type(CONST.ml_selectedsource_type_dict, telegram[10])
|
||||
message["State_Update"]["command"] = self._dictsanitize(CONST.beo4_commanddict, telegram[11])
|
||||
if message["State_Update"]["command"] == 'Go/Play':
|
||||
message["State_Update"]["state"] = 'Play'
|
||||
elif message["State_Update"]["command"] in ['Standby', 'Stop', 'Wind', 'Rewind']:
|
||||
message["State_Update"]["state"] = message["State_Update"]["command"]
|
||||
|
||||
# audio track info long
|
||||
if message.get("payload_type") == "TRACK_INFO_LONG":
|
||||
|
@ -304,6 +311,8 @@ class MLCLIClient(asynchat.async_chat):
|
|||
message["State_Update"]["source"] = source
|
||||
message["State_Update"]["sourceID"] = telegram[11]
|
||||
message["State_Update"]["source_type"] = self._get_type(CONST.ml_selectedsource_type_dict, telegram[11])
|
||||
# This device is playing
|
||||
message["State_Update"]["state"] = 'Play'
|
||||
else:
|
||||
message["State_Update"]["subtype"] = "Undefined: " + self._hexbyte(telegram[9])
|
||||
|
||||
|
@ -319,6 +328,8 @@ class MLCLIClient(asynchat.async_chat):
|
|||
message["State_Update"]["source"] = source
|
||||
message["State_Update"]["sourceID"] = telegram[11]
|
||||
self._get_channel_track(telegram, message)
|
||||
# Device sending goto source command is playing
|
||||
message["State_Update"]["state"] = 'Play'
|
||||
|
||||
# remote request
|
||||
if message.get("payload_type") == "MLGW_REMOTE_BEO4":
|
||||
|
|
|
@ -103,6 +103,9 @@ class MLConfig:
|
|||
for selectCmd in source["selectCmds"]:
|
||||
if gateway_type == 'blgw':
|
||||
# get source information from the BLGW config file
|
||||
if str(source['sourceId']) == '':
|
||||
source_id = self._srcdictsanitize(CONST.beo4_commanddict, source['selectID']).upper()
|
||||
else:
|
||||
source_id = str(source['sourceId'].split(':')[0])
|
||||
source_id = self._srcdictsanitize(CONST.blgw_srcdict, source_id).upper()
|
||||
device['Sources'][str(source["name"])]['source'] = source_id
|
||||
|
@ -142,7 +145,7 @@ class MLConfig:
|
|||
self.log.info('\tFound ' + str(len(CONST.available_sources)) + ' Available Sources [Name, Type]:')
|
||||
for i in range(len(CONST.available_sources)):
|
||||
self.log.info('\t\t' + str(list(CONST.available_sources[i])))
|
||||
self.log.info('Done!\n')
|
||||
self.log.info('\tDone!\n')
|
||||
|
||||
self.log.debug(json.dumps(CONST.gateway, indent=4))
|
||||
self.log.debug(json.dumps(CONST.rooms, indent=4))
|
||||
|
@ -169,6 +172,7 @@ class MLConfig:
|
|||
mlcli.last_message['State_Update']['command'] == "Light Timeout":
|
||||
|
||||
device['ML_ID'] = mlcli.last_message.get('to_device')
|
||||
device['Serial_num'] = 'NA'
|
||||
self.log.info("\tMasterLink ID of product " +
|
||||
device.get('Device') + " is " + device.get('ML_ID') + ".\n")
|
||||
test = False
|
||||
|
@ -177,6 +181,11 @@ class MLConfig:
|
|||
|
||||
else:
|
||||
# If this is a NetLink product then it has a serial number and no ML_ID
|
||||
if device['Device'] == 'BeoMaster 7000':
|
||||
device['ML_ID'] = "AUDIO MASTER"
|
||||
self.log.info("\tNetworkLink ID of product " + device.get('Device') + " is " +
|
||||
device.get('Serial_num') + ". MasterLink ID manually assigned to AUDIO MASTER.\n")
|
||||
else:
|
||||
device['ML_ID'] = 'NA'
|
||||
self.log.info("\tNetworkLink ID of product " + device.get('Device') + " is " +
|
||||
device.get('Serial_num') + ". No MasterLink ID assigned.\n")
|
||||
|
|
Loading…
Reference in a new issue