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
|
#!/usr/bin/python
|
||||||
import os
|
import os
|
||||||
|
import unicodedata
|
||||||
from Foundation import NSAppleScript
|
from Foundation import NSAppleScript
|
||||||
from ScriptingBridge import SBApplication
|
from ScriptingBridge import SBApplication
|
||||||
|
|
||||||
''' Module defining a MusicController class for Apple Music, enables:
|
''' Module defining a MusicController class for Apple Music, enables:
|
||||||
basic transport control of the player,
|
basic transport control of the player,
|
||||||
query of player status,
|
query of player status,
|
||||||
playing existing playlists, and
|
playing existing playlists,
|
||||||
running external appleScripts for more complex control
|
running external appleScripts for more complex control, and
|
||||||
reporting messages in the notification centre'''
|
reporting messages in the notification centre'''
|
||||||
|
|
||||||
PLAYSTATE = dict([
|
PLAYSTATE = dict([
|
||||||
|
@ -26,9 +27,14 @@ class MusicController(object):
|
||||||
|
|
||||||
# Player information
|
# Player information
|
||||||
def get_current_track_info(self):
|
def get_current_track_info(self):
|
||||||
name = str(self.app.currentTrack().name())
|
name = self.app.currentTrack().name()
|
||||||
album = str(self.app.currentTrack().album())
|
album = self.app.currentTrack().album()
|
||||||
artist = str(self.app.currentTrack().artist())
|
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]
|
return [name, album, artist]
|
||||||
|
|
||||||
def get_current_play_state(self):
|
def get_current_play_state(self):
|
||||||
|
@ -48,6 +54,7 @@ class MusicController(object):
|
||||||
elif PLAYSTATE.get(self.app.playerState()) == 'Pause':
|
elif PLAYSTATE.get(self.app.playerState()) == 'Pause':
|
||||||
self.app.playpause()
|
self.app.playpause()
|
||||||
elif PLAYSTATE.get(self.app.playerState()) == 'Stop':
|
elif PLAYSTATE.get(self.app.playerState()) == 'Stop':
|
||||||
|
self. app.setValue_forKey_('true', 'shuffleEnabled')
|
||||||
playlist = self.app.sources().objectWithName_("Library")
|
playlist = self.app.sources().objectWithName_("Library")
|
||||||
playlist.playOnce_(None)
|
playlist.playOnce_(None)
|
||||||
|
|
||||||
|
@ -79,6 +86,12 @@ class MusicController(object):
|
||||||
|
|
||||||
# ########################################################################################
|
# ########################################################################################
|
||||||
# More complex playback control functions
|
# 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'):
|
def set_current_track_position(self, time, mode='Relative'):
|
||||||
if mode == 'Relative':
|
if mode == 'Relative':
|
||||||
# Set playback position in seconds relative to current position
|
# Set playback position in seconds relative to current position
|
||||||
|
@ -102,10 +115,10 @@ class MusicController(object):
|
||||||
s.executeAndReturnError_(None)
|
s.executeAndReturnError_(None)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def notify(message):
|
def notify(message, subtitle):
|
||||||
# Post message in notification center
|
# Post message in notification center
|
||||||
path = os.path.dirname(os.path.abspath(__file__))
|
path = os.path.dirname(os.path.abspath(__file__))
|
||||||
script = 'tell application "' + path + '/Notify.app" to notify("BeoGateway", "' + \
|
script = 'tell application "' + path + '/Notify.app" to notify("BeoGateway", "' + \
|
||||||
message + '")'
|
message + '", "' + subtitle + '")'
|
||||||
s = NSAppleScript.alloc().initWithSource_(script)
|
s = NSAppleScript.alloc().initWithSource_(script)
|
||||||
s.executeAndReturnError_(None)
|
s.executeAndReturnError_(None)
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
# Constants for B&O telegram protocols
|
# Constants for B&O telegram protocols
|
||||||
# ########################################################################################
|
# ########################################################################################
|
||||||
# Config data (set on initialisation)
|
# 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 = []
|
rooms = []
|
||||||
devices = []
|
devices = []
|
||||||
available_sources = []
|
available_sources = []
|
||||||
|
@ -13,7 +20,7 @@ source_type_dict = dict(
|
||||||
[
|
[
|
||||||
("Video Sources", ("TV", "V.AUX/DTV2", "MEDIA", "V.TAPE/V.MEM/DVD2", "DVD", "CAMERA",
|
("Video Sources", ("TV", "V.AUX/DTV2", "MEDIA", "V.TAPE/V.MEM/DVD2", "DVD", "CAMERA",
|
||||||
"SAT/DTV", "PC", "WEB", "DOORCAM", "PHOTO", "USB2", "WEBMEDIA", "AV.IN",
|
"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",
|
"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",
|
"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",
|
"MATRIX_6", "MATRIX_7", "MATRIX_8", "MATRIX_9", "MATRIX_10", "MATRIX_11",
|
||||||
|
@ -262,9 +269,9 @@ ml_command_type_dict = dict(
|
||||||
(0x3C, "TIMER"),
|
(0x3C, "TIMER"),
|
||||||
(0x40, "CLOCK"),
|
(0x40, "CLOCK"),
|
||||||
(0x44, "TRACK_INFO"),
|
(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"),
|
(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"),
|
(0x5C, "LOCK_MANAGER_COMMAND"),
|
||||||
(0x6C, "DISTRIBUTION_REQUEST"),
|
(0x6C, "DISTRIBUTION_REQUEST"),
|
||||||
(0x82, "TRACK_INFO_LONG"),
|
(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
|
# 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
|
# 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
|
# 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"), # ?
|
(0x12, "KEY_LOST"), # ?
|
||||||
# Unknown command with payload of length 1.
|
# Unknown command with payload of length 1.
|
||||||
# bit 0: unknown
|
# bit 0: unknown
|
||||||
|
@ -344,9 +352,11 @@ ml_device_dict = dict(
|
||||||
(0x83, "ALL LINK DEVICES"),
|
(0x83, "ALL LINK DEVICES"),
|
||||||
(0x80, "ALL"),
|
(0x80, "ALL"),
|
||||||
(0xF0, "MLGW"),
|
(0xF0, "MLGW"),
|
||||||
|
(0x21, "BLC NL/ML"),
|
||||||
# Power Master exists in older (pre 1996?) ML implementations. Later revisions enforced the Video Master
|
# 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
|
# 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.
|
# you may see this device type on the network.
|
||||||
|
# reference: https://tidsskrift.dk/daimipb/article/download/7043/6004/0
|
||||||
(0xFF, "POWER MASTER"), # ?
|
(0xFF, "POWER MASTER"), # ?
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -433,7 +443,7 @@ destselectordict = dict(
|
||||||
[
|
[
|
||||||
(0x00, "Video Source"),
|
(0x00, "Video Source"),
|
||||||
(0x01, "Audio Source"),
|
(0x01, "Audio Source"),
|
||||||
(0x05, "V.TAPE/V.MEM"),
|
(0x05, "Secondary Video Source (V.TAPE/V.MEM)"),
|
||||||
(0x0F, "All Products"),
|
(0x0F, "All Products"),
|
||||||
(0x1B, "MLGW"),
|
(0x1B, "MLGW"),
|
||||||
]
|
]
|
||||||
|
@ -451,9 +461,9 @@ mlgw_soundstatusdict = dict([(0x00, "Not muted"), (0x01, "Muted")])
|
||||||
mlgw_speakermodedict = dict(
|
mlgw_speakermodedict = dict(
|
||||||
[
|
[
|
||||||
(0x01, "Center channel"),
|
(0x01, "Center channel"),
|
||||||
(0x02, "2ch stereo"),
|
(0x02, "2 channel stereo"),
|
||||||
(0x03, "Front surround"),
|
(0x03, "Front surround"),
|
||||||
(0x04, "4ch stereo"),
|
(0x04, "4 channel stereo"),
|
||||||
(0x05, "Full surround"),
|
(0x05, "Full surround"),
|
||||||
(0xFD, "<all>"), # Dummy for 'Listen for all modes'
|
(0xFD, "<all>"), # Dummy for 'Listen for all modes'
|
||||||
]
|
]
|
||||||
|
|
|
@ -193,7 +193,10 @@ class MLCLIClient(asynchat.async_chat):
|
||||||
# Decode header
|
# Decode header
|
||||||
message = OrderedDict()
|
message = OrderedDict()
|
||||||
self._get_device_info(message, telegram)
|
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["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_device"] = self._get_device_name(self._dictsanitize(CONST.ml_device_dict, telegram[0]))
|
||||||
message["to_source"] = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[4])
|
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"]["sourceID"] = telegram[10]
|
||||||
message["State_Update"]["source_type"] = self._get_type(CONST.ml_selectedsource_type_dict, 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])
|
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
|
# audio track info long
|
||||||
if message.get("payload_type") == "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"]["source"] = source
|
||||||
message["State_Update"]["sourceID"] = telegram[11]
|
message["State_Update"]["sourceID"] = telegram[11]
|
||||||
message["State_Update"]["source_type"] = self._get_type(CONST.ml_selectedsource_type_dict, 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:
|
else:
|
||||||
message["State_Update"]["subtype"] = "Undefined: " + self._hexbyte(telegram[9])
|
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"]["source"] = source
|
||||||
message["State_Update"]["sourceID"] = telegram[11]
|
message["State_Update"]["sourceID"] = telegram[11]
|
||||||
self._get_channel_track(telegram, message)
|
self._get_channel_track(telegram, message)
|
||||||
|
# Device sending goto source command is playing
|
||||||
|
message["State_Update"]["state"] = 'Play'
|
||||||
|
|
||||||
# remote request
|
# remote request
|
||||||
if message.get("payload_type") == "MLGW_REMOTE_BEO4":
|
if message.get("payload_type") == "MLGW_REMOTE_BEO4":
|
||||||
|
|
|
@ -103,8 +103,11 @@ class MLConfig:
|
||||||
for selectCmd in source["selectCmds"]:
|
for selectCmd in source["selectCmds"]:
|
||||||
if gateway_type == 'blgw':
|
if gateway_type == 'blgw':
|
||||||
# get source information from the BLGW config file
|
# get source information from the BLGW config file
|
||||||
source_id = str(source['sourceId'].split(':')[0])
|
if str(source['sourceId']) == '':
|
||||||
source_id = self._srcdictsanitize(CONST.blgw_srcdict, source_id).upper()
|
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
|
device['Sources'][str(source["name"])]['source'] = source_id
|
||||||
device['Sources'][str(source["name"])]['uniqueID'] = str(source['sourceId'])
|
device['Sources'][str(source["name"])]['uniqueID'] = str(source['sourceId'])
|
||||||
else:
|
else:
|
||||||
|
@ -142,7 +145,7 @@ class MLConfig:
|
||||||
self.log.info('\tFound ' + str(len(CONST.available_sources)) + ' Available Sources [Name, Type]:')
|
self.log.info('\tFound ' + str(len(CONST.available_sources)) + ' Available Sources [Name, Type]:')
|
||||||
for i in range(len(CONST.available_sources)):
|
for i in range(len(CONST.available_sources)):
|
||||||
self.log.info('\t\t' + str(list(CONST.available_sources[i])))
|
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.gateway, indent=4))
|
||||||
self.log.debug(json.dumps(CONST.rooms, 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":
|
mlcli.last_message['State_Update']['command'] == "Light Timeout":
|
||||||
|
|
||||||
device['ML_ID'] = mlcli.last_message.get('to_device')
|
device['ML_ID'] = mlcli.last_message.get('to_device')
|
||||||
|
device['Serial_num'] = 'NA'
|
||||||
self.log.info("\tMasterLink ID of product " +
|
self.log.info("\tMasterLink ID of product " +
|
||||||
device.get('Device') + " is " + device.get('ML_ID') + ".\n")
|
device.get('Device') + " is " + device.get('ML_ID') + ".\n")
|
||||||
test = False
|
test = False
|
||||||
|
@ -177,9 +181,14 @@ class MLConfig:
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# If this is a NetLink product then it has a serial number and no ML_ID
|
# If this is a NetLink product then it has a serial number and no ML_ID
|
||||||
device['ML_ID'] = 'NA'
|
if device['Device'] == 'BeoMaster 7000':
|
||||||
self.log.info("\tNetworkLink ID of product " + device.get('Device') + " is " +
|
device['ML_ID'] = "AUDIO MASTER"
|
||||||
device.get('Serial_num') + ". No MasterLink ID assigned.\n")
|
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")
|
||||||
|
|
||||||
self.log.debug(json.dumps(device, indent=4))
|
self.log.debug(json.dumps(device, indent=4))
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue